Skip to content
Open RaamTrade

Indicators

Indicators are computed once per bar and feed into your trading logic. The transpiler enforces three rules: declare them at the top of execute(), give every one a source, and only chain onto single-value outputs.

Declaration

All indicator constructions must appear at the top of execute(), before any other statement. The transpiler hoists them so each indicator receives every bar’s data — not just the bars where a conditional branch fires.

class Declarations(Strategy):
fast_period = Param(7, min=2, max=50)
slow_period = Param(21, min=10, max=200)
trade = Symbol()
sizing = FixedCapital()
def execute(self):
# ✅ All indicators first
fast = SMA(self.fast_period, on=self.trade)
slow = SMA(self.slow_period, on=self.trade)
rsi = RSI(14) # implicit source: trade symbol
smooth = EMA(5, on=rsi) # chained — EMA of RSI
high_sma = SMA(20, on=self.trade.high) # specific OHLCV field
# Logic starts AFTER all indicators
self.stop_loss(2.0)
if fast.crossed_above(slow) and smooth.value < 70 and high_sma.value > 0:
self.buy(self.trade, reason="Fast crossed slow")

Putting an indicator inside an if or for block emits RC2_IND_IN_BODY.

on= source

The on= keyword names the indicator’s input:

Pattern What it means
SMA(20, on=self.trade) Close price of the trade symbol.
SMA(20, on=self.spy) Close price of a data feed.
SMA(20, on=self.trade.high) A specific OHLCV field.
EMA(5, on=rsi) Chain — input is another indicator’s output.
SMA(20) Omitted — defaults to the trade symbol’s close.

Explicit on=self.trade is recommended even when optional — it makes intent obvious to a reader (and to a code review six months from now).

Built-in indicators

Name Values Notes
SMA Single decimal Simple Moving Average.
EMA Single decimal Exponential Moving Average.
WMA Single decimal Weighted Moving Average.
RSI Single decimal Relative Strength Index.
MACD .line, .signal, .histogram Trend momentum (EMA difference).
BollingerBands .upper, .mid, .lower SMA ± stddev envelope.
ATR Single decimal Average True Range.
CCI Single decimal Commodity Channel Index.
ROC Single decimal Rate of Change (percentage momentum).
Momentum Single decimal Price momentum (close − close N bars ago).
StdDev Single decimal Rolling standard deviation.
Aroon .up, .down, .oscillator Trend strength.

See the Indicator Catalog for parameters, formulas, and warmup requirements.

Reading values

fast.value # current value (explicit)
fast > slow # comparison reads .value implicitly on both sides
fast > 100 # comparison with a number
fast.value_at(1) # value from 1 bar ago
fast.value_at(5) # any past bar (within the history window)

value_at(0) is equivalent to .value. Negative offsets are not supported.

Crossover detection

# Indicator vs indicator
fast.crossed_above(slow) # True for the single bar fast goes from ≤ slow to > slow
fast.crossed_below(slow) # True for the single bar fast goes from ≥ slow to < slow
# Indicator vs numeric threshold
rsi.crossed_above(30)
rsi.crossed_below(self.overbought)

Both sides must be single-value decimals. Multi-value indicators (Aroon, MACD, BollingerBands) can’t use crossover helpers directly — extract a field first:

aroon = Aroon(14, on=self.trade)
aroon.crossed_above(50) # ❌ RC2_CROSS_MULTIVALUE
aroon.up.crossed_above(50) # ❌ also refused — field-level crossover is not implemented

Both forms are refused today. Express a multi-value crossover manually instead:

aroon = Aroon(14, on=self.trade)
if aroon.up > 70 and aroon.down < 30:
...

Chaining

rsi = RSI(14, on=self.trade)
smooth_rsi = EMA(5, on=rsi) # chain a single-value indicator
high_ema = EMA(10, on=self.trade.high) # chain onto an OHLCV field

Chaining onto a multi-value indicator is rejected, in two different ways — and the first message asks for the second, which is also refused:

So there is no way to chain onto a multi-value indicator right now. Compute the field into a local and compare it directly, or reproduce the smoothing yourself in persistent state.

Warmup

Each indicator declares the number of bars it needs before producing valid output. The platform automatically prefills history so execute() only runs once every indicator is ready. You never see the warmup phase — your first execute() call already has fully-warmed indicators.

See Warmup → for the full lifecycle.

Common diagnostics

Code What it means
RC2_IND_IN_BODY Indicator declared inside a conditional or loop.
RC2_IND_ON The indicator needs on= because no trading symbol is declared.
RC2_IND_ON_UNKNOWN on= refers to an unknown symbol slot or indicator.
RC2_IND_ON_FIELD The OHLCV field doesn’t exist on the source.
RC2_IND_ON_MULTIVALUE on= points at a multi-value indicator without naming an output.
RC2_IND_ON_MULTIVALUE_CHAIN Chained onto a named output of a multi-value indicator.
RC2_IND_PERIOD No first positional argument. (It fires even for the one indicator that has no parameters — see Volume.)
RC2_IND_CYCLE An indicator references itself through on=.
RC2_UNKNOWN_INDICATOR Unknown indicator name.
RC2_CROSS_MULTIVALUE crossed_above / crossed_below on a multi-value indicator.