Your First Strategy
Build, backtest, and read an EMA-crossover strategy in Strategy Studio in under five minutes.
Build a working EMA crossover from a blank canvas, run a backtest, and read the results. About five minutes. Open /studio on a computer (Studio is desktop-only).
The finished graph: when a fast EMA crosses above a slow EMA, go long; when it crosses back below, close. (If you'd rather start from a working graph, New opens the template gallery: see Examples & Templates.)
1. Set the timeframe
Drag a data.timeframe node onto the canvas. Leave it at 1h. There is exactly one of these per graph — it sets the global cadence everything else inherits.
2. Add the data source
Drag a data.candles node. Leave its coin on Current market (the default), or pick a market such as BTC. A backtest runs Current market on the fallback market in the Inspector's Markets section. You'll use its close output to feed the indicators.
3. Add two EMAs
Drag two ind.ema nodes:
- Fast:
period = 10 - Slow:
period = 30
Wire data.candles.close → each EMA's value input. (The value input is number|candle — you could wire candle instead and pick the source in the param, but close is simplest.)
4. Detect the cross
Drag a logic.cross node. Wire:
- fast EMA
out→cross.a - slow EMA
out→cross.b
logic.cross emits events: up pulses the bar A crosses above B, down the bar it crosses below. These are signal<boolean> — exactly what an exec node's fire input wants. (Exec nodes default to fireMode: "edge", so even a sustained logic.compare boolean wired into fire would fire once on its rising edge rather than every bar — but an explicit event keeps the intent obvious. See signal vs level.)
5. Wire execution
Drag an exec.market node (set side = buy, sizeFallback = 0.01) and an exec.closePosition node.
cross.up→exec.market.firecross.down→exec.closePosition.fire
Both exec nodes keep fireOnBarClose: true, so they act once per bar close — no intra-bar surprises.
6. Add a position cap (optional but good habit)
Drag a risk.maxPositionSize node (coin = Current market, maxNotional = 5000) and a risk.preTradeValidate node. Wire:
exec.market.intent→preTradeValidate.intentrisk.maxPositionSize.ok→preTradeValidate.ok
Now the engine drops the entry at the submit barrier whenever the position would exceed $5,000 notional.
7. Run a backtest
Make sure the Issues tab is clean (no red errors). With nothing selected, check the Backtest range in the Inspector (30 days by default): the first 30 bars warm up the slowest indicator, the 30-period EMA. Pick Backtest in the toolbar's mode switch and hit Run. The engine replays historical 1h candles over the configured range in a worker.
8. Read the results
The bottom panel streams four things:
- Timeline: the equity curve over the backtest. A rising curve is the headline.
- Orders — every fill: time, side, size, fill price, fee. This is where you confirm the strategy actually traded when you expected.
- Plots — any
debug.plotseries (none yet — add adebug.plotonind.ema.outto overlay the EMAs). - Console — log lines, including
order:rejectedreasons (e.g. sub-min-notional) and risk-gate drops.
If you see no orders, the most common causes are: the range too short for the indicators to leave NaN or for a cross to occur, or sizeFallback producing a sub-$10 notional that gets rejected (check Console).
Next steps
- Understand *why* the fills land where they do: Backtest Modeling (fill timing, fees, slippage, funding).
- Add a higher-timeframe trend filter with a per-node timeframe override.
- Every finished run is kept in the Runs tab: change a period, run again, and tick both rows to compare them.
- Browse the 12 shipped templates for patterns to remix.
- Stuck? See the FAQ & Troubleshooting.