Strategy System
How strategies are defined — the DSL structure, all available functions, operators, and a complete example.
Overview
A strategy is a JSON object with five top-level keys:configuration, inputs,conditions, score, and decision. Each serves a distinct role in the evaluation pipeline.
strategySnapshotJson field. Internally, the engine parses and evaluates it against historical OHLCV data candle by candle.#Configuration
Defines the execution timeframe. The engine uses this to determine which candle interval to evaluate the strategy against.
"configuration": {
"timeframe": "1H"
}Supported timeframes
| Value | Description |
|---|---|
| 15M | 15-minute candles |
| 30M | 30-minute candles |
| 1H | 1-hour candles (most common) |
| 2H | 2-hour candles |
| 4H | 4-hour candles |
| 1D | Daily candles |
#Inputs
Inputs are named, reusable computed values. They are evaluated once per candle before conditions are checked. An input can reference market data, built-in functions, or other previously defined inputs.
"inputs": {
"emaFast": "ema(close, 9)",
"emaSlow": "ema(close, 21)",
"rsiValue": "rsi(close, 14)",
"atrValue": "atr(14)",
"volSpike": "volumeSpike(1.5)"
}Available market data
closeopenhighlowvolumeRules
- Input names must be unique.
- Inputs can reference other inputs defined above them (order matters).
- Circular references are not allowed.
- Input expressions must resolve to a numeric value.
#Conditions
Conditions are named boolean expressions. They are evaluated per candle using inputs, market data, and comparison operators. A condition must resolve to true orfalse.
"conditions": {
"trendUp": "emaFast > emaSlow",
"rsiHealthy": "rsiValue > 40 AND rsiValue < 65",
"breakout": "crossUp(close, emaSlow)",
"volConfirm": "volSpike > 1"
}Supported operators
| Operator | Type | Example |
|---|---|---|
| > | Relational | "rsiValue > 40" |
| < | Relational | "rsiValue < 70" |
| >= | Relational | "score >= 50" |
| <= | Relational | "atrValue <= 200" |
| == | Equality | "timeframe == 1H" |
| != | Equality | "close != open" |
| AND | Logical | "condA AND condB" |
| OR | Logical | "condA OR condB" |
Rules
- All variables referenced in a condition must be defined in
inputs. - Conditions must resolve to a boolean. A numeric value of 0 is false; non-zero is true.
- No future data may be referenced. All expressions use only current or past candle data.
- Randomness is not permitted — strategies must be fully deterministic.
#Score
The score block assigns a numeric weight to each condition. On every candle, the engine sums the scores of all conditions that evaluate to true. This total is the candle's score.
"score": {
"trendUp": 20,
"rsiHealthy": 30,
"breakout": 40,
"volConfirm": 10
}The score values are arbitrary integers. What matters is their relative weight. A condition with score 40 contributes twice as much as one with score 20.
#Decision
The decision block defines the entry rule. The engine evaluates this expression per candle after computing the total score. When it evaluates to true, a new trade is opened at the close of that candle.
"decision": {
"entry": "score >= 50"
}The decision expression can reference score (the total for the candle) as well as any defined condition by name.
"decision": {
"entry": "score >= 50 AND breakout"
}#Built-in Functions
Indicators
| Function | Description |
|---|---|
| ema(series, period) | Exponential moving average of series over period candles. |
| sma(series, period) | Simple moving average of series over period candles. |
| rsi(series, period) | Relative Strength Index. Returns 0–100. |
| atr(period) | Average True Range over period candles. |
Signals
| Function | Description |
|---|---|
| crossUp(a, b) | Returns true on the candle where a crosses above b. |
| crossDown(a, b) | Returns true on the candle where a crosses below b. |
Series operators
| Function | Description |
|---|---|
| highest(series, n) | Highest value of series over the last n candles. |
| lowest(series, n) | Lowest value of series over the last n candles. |
| change(series) | Difference between the current and previous candle value. |
Volume
| Function | Description |
|---|---|
| volumeSma(period) | Simple moving average of volume over period candles. |
| volumeSpike(multiplier) | Returns current volume / volumeSma. >1 means volume is above average. |
Price action
| Function | Description |
|---|---|
| body() | Absolute difference between open and close. |
| range() | Absolute difference between high and low. |
| upperWick() | Size of the upper wick: high - max(open, close). |
| lowerWick() | Size of the lower wick: min(open, close) - low. |
Math
| Function | Description |
|---|---|
| abs(x) | Absolute value. |
| min(a, b) | Minimum of two values. |
| max(a, b) | Maximum of two values. |
#Full Strategy Example
A trend-following strategy with EMA crossover, RSI confirmation, and volume spike entry.
{
"configuration": {
"timeframe": "1H"
},
"inputs": {
"emaFast": "ema(close, 9)",
"emaSlow": "ema(close, 21)",
"rsiValue": "rsi(close, 14)",
"volRatio": "volumeSpike(1.3)"
},
"conditions": {
"trendUp": "emaFast > emaSlow",
"rsiHealthy": "rsiValue > 40 AND rsiValue < 65",
"volConfirm": "volRatio > 1",
"crossover": "crossUp(emaFast, emaSlow)"
},
"score": {
"trendUp": 20,
"rsiHealthy": 25,
"volConfirm": 15,
"crossover": 40
},
"decision": {
"entry": "score >= 60"
}
}Invalid Patterns
What to avoid
- Referencing undefined inputs in conditions or the decision block.
- Using conditions that do not resolve to a boolean (e.g., a numeric expression).
- Referencing future data (lookahead bias).
- Circular input references.
- Using randomness or non-deterministic logic of any kind.
"conditions": {
"breakout": "close > resistance" // ERROR: 'resistance' not defined in inputs
}