Strategy Anatomy
תוכן זה אינו זמין עדיין בשפה שלך.
Every RaamCode strategy follows the same shape. The transpiler enforces it.
class StrategyName(Strategy): """Description shown in the catalog."""
# ── Class body: Param(), Symbol(), and sizing ── fast_period = Param(7, min=2, max=50) slow_period = Param(21, min=10, max=200) trade = Symbol() sizing = FixedCapital()
def execute(self): # ── 1. Indicators at the top (the transpiler hoists them) ── fast = SMA(self.fast_period, on=self.trade) slow = SMA(self.slow_period, on=self.trade)
# ── 2. Exit rules (optional, can be dynamic) ── self.stop_loss(2.0)
# ── 3. Order logic ── if fast.crossed_above(slow) and self.trade.is_flat: self.buy(self.trade, reason="Bullish crossover")
if fast.crossed_below(slow) and self.trade.is_long: self.sell(self.trade, self.trade.position, reason="Bearish crossover")Required pieces
| Piece | Required? | Notes |
|---|---|---|
Inherit from Strategy |
Yes | The only valid base class. |
| Class name | Yes | Becomes the strategy name in the catalog. One class per file. |
| Docstring | Recommended | Shown as the catalog description. |
At least one Symbol() |
Yes | Otherwise: RC2_NO_SYMBOL. |
One sizing = … declaration |
Yes | Otherwise: RC2_NO_SIZING. |
def execute(self) |
Yes | Otherwise: RC2_NO_EXECUTE. |
| Helper methods | Optional | def name(self, args), callable from execute(). |
Class body rules
The class body may contain only:
Param(...)assignmentsSymbol(...)assignments- A single
sizing = ...assignment - Plain literal assignments (e.g.
position_size = 100)
Anything else — function calls, control flow, arithmetic, expressions — emits RC2_CLASS_BODY.
class Bad(Strategy): trade = Symbol() sizing = FixedCapital() threshold = 30 + 5 # ❌ RC2_CLASS_BODY — no expressions
def execute(self): if self.trade.is_flat: self.buy(self.trade)class Good(Strategy): trade = Symbol() sizing = FixedCapital() threshold = 35 # ✅ plain literal — persistent state overbought = Param(70) # ✅ Param call
def execute(self): rsi = RSI(14, on=self.trade) if rsi.value > self.overbought and self.trade.is_long: self.sell(self.trade, self.trade.position)Reading a class-body literal alongside an indicator does not compile today
A plain-literal class-body assignment declares persistent state, and reading it back (self.threshold) from an execute() that also declares an indicator currently produces a raw C# error (CS0103) rather than a RaamCode diagnostic. Until that is fixed, express a constant you want to read as a Param() — which is what you usually want anyway, since a Param is tunable and shows up in the optimizer.
Execution order inside execute()
The transpiler hoists every indicator declaration to the top of execute() so each indicator receives every bar — even when your if branch is False. The rest of the body runs in declaration order.
If execute() finishes without firing a signal, the strategy holds its current position. There is no implicit “do nothing else” — holding is the default.
Mode-specific entry points
| Method | When it runs | Common use |
|---|---|---|
execute(self) |
Every bar after warmup | The trading logic. |
| Helper methods | When called by execute() |
Risk-sizing math, signal helpers. |
Multiple symbols
When a strategy declares more than one Symbol(), exactly one must be marked trade=True — that symbol is the only one orders can be placed against. The others are data feeds (read-only OHLCV).
class SPYFiltered(Strategy): trade = Symbol(session=USEquityDaily, trade=True) # the trading slot spy = Symbol(ticker="SPY") # data feed sizing = FixedCapital()
def execute(self): spy_sma = SMA(50, on=self.spy) if self.spy.close > spy_sma.value and self.trade.is_flat: self.buy(self.trade)Common diagnostics
| Code | What it means |
|---|---|
RC2_NO_SYMBOL |
No Symbol() declared in the class body. |
RC2_NO_TRADE |
Multiple symbols but none marked trade=True. |
RC2_NO_SIZING |
Missing sizing = ... assignment. |
RC2_NO_EXECUTE |
Strategy has no def execute(self). |
RC2_CLASS_BODY |
Disallowed construct in the class body. |