Strategy spec
The StrategySpec JSON schema — indicators, clause trees, filters, examples. The single source of truth that drives both backtest and live execution.
A StrategySpec is a single JSON document that describes a complete trading strategy. The same document is consumed by the backtester and by the live engine, so what you test is what you ship. There is no compiled DSL, no hidden state.
Top-level shape
{
"name": "RSI mean reversion EURUSD",
"symbol": "EURUSD",
"entryTimeframe": "5m",
"indicators": [ ... ],
"entryLong": { ... clause tree ... },
"entryShort": { ... clause tree ... },
"exit": { "stopLoss": ..., "takeProfit": ..., "breakeven": ... },
"risk": { "perTradePct": 0.5, "leverage": 10, "dailyDdCap": 3 },
"sessions": [ { "tz": "Europe/London", "from": "08:00", "to": "16:00" } ],
"news": { "impact": "high", "blockMinutesBefore": 15, "blockMinutesAfter": 15 },
"htfTrend": { "indicator": "ema200_1h", "rule": "priceAbove" }
}Indicators
Each indicator is declared once and assigned a stable id. The id is what clauses and filters reference. Every indicator exposes one or more lanes.
ema— exponential moving average. Lane: the id itself.sma— simple moving average. Lane: the id itself.rsi— relative strength index, 0–100.bb— Bollinger Bands. Lanes<id>_upper,<id>_lower,<id>_mid.atr— average true range, used for volatility-aware exits.macd— moving average convergence/divergence. Lanes<id>_line,<id>_signal,<id>_hist.stoch— stochastic oscillator. Lanes<id>_k,<id>_d.
A typical indicators block:
"indicators": [
{ "id": "ema50", "type": "ema", "period": 50, "source": "close", "timeframe": "5m" },
{ "id": "rsi14", "type": "rsi", "period": 14, "timeframe": "5m" },
{ "id": "bb20", "type": "bb", "period": 20, "stddev": 2, "timeframe": "1m" }
]The clause tree
Entries are described as a boolean tree with three node kinds: all (logical AND), any (logical OR), and not (negation). Leaves are comparison clauses with an operator (gt, lt, gte, lte, eq, crossesAbove, crossesBelow) and two operands. Operands are either indicator lane names, OHLC fields (close,open, high, low), or constants.
"entryLong": {
"all": [
{ "op": "lt", "left": "rsi14", "right": 30 },
{ "op": "gt", "left": "close", "right": "bb20_1m_lower" }
]
}Exits
The exit block supports three mechanisms that can be combined. stopLoss and takeProfit are expressed in pips, points, or as a multiple of an ATR lane.breakeven moves the stop to entry once a configurable trigger profit is reached.
"exit": {
"stopLoss": { "atr": "atr14", "mult": 1.5 },
"takeProfit": { "atr": "atr14", "mult": 3.0 },
"breakeven": { "triggerAtr": "atr14", "triggerMult": 1.0 }
}Risk
perTradePct— percent of account equity risked per trade, sized off the stop distance.leverage— informational cap; the broker enforces actual leverage.dailyDdCap— percent drawdown from session-open equity that pauses the droplet automatically.
Filters
Sessions
Entries only fire inside any of the time windows in sessions. Windows are timezone-aware so daylight saving is handled implicitly.
News
The news filter consumes a vendor calendar and blocks new entries within a symmetric window around events of the configured impact level. Existing positions are not closed.
htfTrend
Higher-timeframe trend gate. A common pattern: only allow longs when 1H price is above the 200 EMA, only allow shorts when 1H price is below it. Reference the lane via its id, e.g.ema200_1h.
Patterns
A pattern clause matches structural events such as bullish_fvg (fair value gap), bearish_engulfing, or liquidity_sweep. Patterns can appear inside the clause tree like any other condition.
Example 1 — RSI mean reversion (long only)
{
"name": "RSI MR EURUSD",
"symbol": "EURUSD",
"entryTimeframe": "5m",
"indicators": [
{ "id": "rsi14", "type": "rsi", "period": 14, "timeframe": "5m" },
{ "id": "atr14", "type": "atr", "period": 14, "timeframe": "5m" }
],
"entryLong": { "all": [ { "op": "lt", "left": "rsi14", "right": 28 } ] },
"entryShort": null,
"exit": {
"stopLoss": { "atr": "atr14", "mult": 1.5 },
"takeProfit": { "atr": "atr14", "mult": 2.5 }
},
"risk": { "perTradePct": 0.5, "leverage": 10, "dailyDdCap": 3 }
}Example 2 — EMA crossover with HTF filter
{
"name": "EMA cross with 1H trend",
"symbol": "XAUUSD",
"entryTimeframe": "15m",
"indicators": [
{ "id": "ema20", "type": "ema", "period": 20, "timeframe": "15m" },
{ "id": "ema50", "type": "ema", "period": 50, "timeframe": "15m" },
{ "id": "ema200_1h", "type": "ema", "period": 200, "timeframe": "1h" }
],
"entryLong": { "all": [ { "op": "crossesAbove", "left": "ema20", "right": "ema50" } ] },
"entryShort": { "all": [ { "op": "crossesBelow", "left": "ema20", "right": "ema50" } ] },
"htfTrend": { "indicator": "ema200_1h", "rule": "priceAbove" },
"exit": { "stopLoss": { "pips": 250 }, "takeProfit": { "pips": 500 } },
"risk": { "perTradePct": 0.4, "leverage": 20, "dailyDdCap": 4 }
}Example 3 — Bollinger breakout
{
"name": "BB breakout NAS100",
"symbol": "NAS100",
"entryTimeframe": "1m",
"indicators": [
{ "id": "bb20", "type": "bb", "period": 20, "stddev": 2, "timeframe": "1m" },
{ "id": "atr14", "type": "atr", "period": 14, "timeframe": "1m" }
],
"entryLong": { "all": [ { "op": "crossesAbove", "left": "close", "right": "bb20_1m_upper" } ] },
"entryShort": { "all": [ { "op": "crossesBelow", "left": "close", "right": "bb20_1m_lower" } ] },
"exit": { "stopLoss": { "atr": "atr14", "mult": 1.2 }, "takeProfit": { "atr": "atr14", "mult": 2.0 } },
"risk": { "perTradePct": 0.3, "leverage": 20, "dailyDdCap": 2 }
}