Expression Syntax
Complete guide to writing expressions in AlgoHive analysis blocks.
Expressions are the language you use to describe calculations and conditions inside your strategy.
They’re used in three places:
- Analysis blocks: compute values/signals across the whole chart (time-series).
- Entry rules (
when): decide when to open a position (evaluated bar-by-bar). - Manage rules (
when, dynamic levels): manage an open position (evaluated bar-by-bar).
This is not Python. Some examples use a python code fence only for syntax highlighting — the language is AlgoHive’s expression grammar.
The mental model
Think of expressions as two layers:
- Analysis layer (precompute): indicators, crossovers, filters → outputs like
signals.ma_cross_up - Trading layer (decide): entry and manage rules that mostly reference those outputs
Put time-series math (indicators, lookbacks, crossovers) in Analysis. Keep Entry/Manage rules simple: reference signals/values, plus position helpers for manage.
Where expressions run (and what you can use)
| Where | What it’s for | Runs as | What you should write |
|---|---|---|---|
| Analysis outputs | build signals + metrics | time-series | indicators, comparisons, crossovers |
Entry when | open a position | per-bar boolean | signals.ma_cross_up AND filters.ok |
| Manage rules | exit/stop/take-profit logic | per-bar boolean/number | position_pnl_r > 2 or signals.exit_now |
References
Data sources (market data)
Access data source columns with dot notation:
btc.close
btc.high
btc.low
btc.open
btc.volumeFor custom data sources, it’s the same pattern: alias.column (whatever columns your data source returns).
Always use the explicit prefix (btc.close, not just close). It prevents ambiguity when you have multiple data sources.
Analysis block outputs
Reference analysis outputs using:
block.outputfor multi-output blocksblockfor single-output blocks (simple blocks)
signals.ma_cross_up
indicators.rsi
trend.fast_ma
daily_trend # single-output blockOperators
Arithmetic
a + b
a - b
a * b
a / bComparisons (return boolean)
a > b
a < b
a >= b
a <= b
a == b
a != bBoolean logic
a AND b
a OR b
NOT aFunctions
Indicators (analysis blocks)
Indicators are called like:
RSI(btc.close, 14)
EMA(btc.close, 20)
ATR(btc.high, btc.low, btc.close, 14)
MACD(btc.close, 12, 26, 9)Some indicators return structs (multiple outputs):
MACD(btc.close, 12, 26, 9).line
MACD(btc.close, 12, 26, 9).signal
BOLLINGER(btc.close, 20, 2).upper
STOCH(btc.high, btc.low, btc.close, 14, 3, 3).kCrossover helpers (analysis blocks)
These are implemented in the engine and are the correct way to express “crosses above/below”:
cross_above(fast_ma, slow_ma)
cross_below(rsi, 70)Other helpers (analysis blocks)
rising(btc.close, 5)
falling(rsi, 3)
HIGHEST(btc.high, 20)
LOWEST(btc.low, 20)
STDEV(btc.close, 20)
CHANGE(btc.close, 1)
PCT_CHANGE(btc.close, 1)
IF(condition, thenValue, elseValue)Time helpers (anywhere)
HOUR_UTC()
DAY_OF_WEEK()
IS_WEEKEND()
IS_DATE("2026-02-02")Lookback (previous bars)
Use lookbacks in analysis outputs:
btc.close[1]
rsi[1]
range_high[5]Avoid using [...] lookbacks directly in Entry conditions. Entry conditions run per-bar and are intended to reference analysis outputs (e.g. signals.cross_up). If you need “previous bar” logic for entry, compute it in analysis first.
Dynamic lookback (manage only)
Manage rules can do dynamic lookbacks for advanced workflows (example: “read the value at entry bar”):
my_block.stop_at_entry: my_block.stop_loss[entry_bar()]Dynamic lookbacks require an open position and are only available during Manage rule evaluation.
Constants you can tune (Value Blocks)
If you want adjustable constants (periods, thresholds), define them as Value Blocks and reference them by name:
fast_period: 9
slow_period: 21
fast_ma: EMA(btc.close, fast_period)
slow_ma: EMA(btc.close, slow_period)This works because value blocks are treated as numeric constants where needed (for example indicator periods).
Practical recipes
1) MA crossover entry (recommended structure)
Analysis block outputs (signals):
fast_ma: EMA(btc.close, fast_period)
slow_ma: EMA(btc.close, slow_period)
ma_cross_up: cross_above(fast_ma, slow_ma)
ma_cross_down: cross_below(fast_ma, slow_ma)Entry when:
signals.ma_cross_up2) RSI threshold + “cross down” exit signal
Analysis outputs:
rsi: RSI(btc.close, 14)
oversold: rsi < 30
overbought: rsi > 70
rsi_cross_down: cross_below(rsi, 70)Entry when:
signals.oversoldManage rule when (exit):
signals.rsi_cross_down3) Manage rule using position context
Use these only in Manage rules:
position_pnl_r > 2
bars_since_entry() > 24What’s allowed where (quick reference)
| Feature | Analysis | Entry | Manage |
|---|---|---|---|
btc.close, alias.column | ✅ | ✅ | ✅ |
signals.foo, block.value | ✅ | ✅ | ✅ |
Indicators like RSI(...) | ✅ | ⚠️ (not recommended) | ⚠️ (not recommended) |
cross_above, cross_below | ✅ | ⚠️ (not recommended) | ⚠️ (not recommended) |
Lookback [1] | ✅ | ❌ (compute in analysis) | ⚠️ (use sparingly) |
Dynamic lookback [entry_bar()] | ❌ | ❌ | ✅ |
Position vars (position_pnl_r, entry_price, …) | ❌ | ❌ | ✅ |
Trade/portfolio vars (in_position, daily_trades, …) | ❌ | ✅ | ✅ |
Time funcs (HOUR_UTC(), IS_WEEKEND(), …) | ✅ | ✅ | ✅ |
If something feels “stateful” (entry price, PnL, time held), it belongs in Manage, not Analysis/Entry.