Strategy & Parameters
Strategies are TypeScript, written and saved in the Editor module, then run from Backtest's
Configure tab. This page covers the shape of a strategy, what ctx gives you, and how
parameter sweeps work.
Writing a strategy
A strategy exports onBar(bar, ctx) (called once per bar) and optionally init(ctx) (called
once before the run — register indicators here so they're precomputed).
export const config = { params: { fast: 10, slow: 30 } };
export function init(ctx: StrategyContext) {
// Register indicators once; the engine precomputes and caches the series.
ctx.fast = ctx.indicator('sma', { period: ctx.params.fast ?? 10 });
ctx.slow = ctx.indicator('sma', { period: ctx.params.slow ?? 30 });
}
export function onBar(bar: OHLCV, ctx: StrategyContext) {
if (ctx.crossover(ctx.fast, ctx.slow) && ctx.position.isFlat) {
ctx.buy({ qty: ctx.size.percentOfEquity(90) });
} else if (ctx.crossunder(ctx.fast, ctx.slow) && ctx.position.isLong) {
ctx.sell({ qty: ctx.position.qty });
}
}
The one rule that matters most: fills happen at the NEXT bar's open. You decide on bar i, you get filled at bar i+1's open. There are no same-bar fills, which is what keeps results honest.
What's on ctx:
- Indicators —
ctx.indicator(type, params?, opts?)with 50 registered types (sma/ema/rsi/macd/bollinger/atr/supertrend/chop/… — see the indicator catalogue). Cursors expose.value,.at(lookback),.output(name). Note the two VWAPs:vwapis cumulative over the whole run;svwapresets at each UTC day boundary (the session VWAP you want for intraday anchoring). - Higher timeframes —
ctx.indicator('sma', { period: 20 }, { timeframe: '1h' })computes over resampled bars with strict no-lookahead (values only update when a bucket completes). Note: an HTF crossover condition stays true for every base bar of the bucket after a crossing — guard entries with position checks. - Orders —
buy,sell,limit,stop,stopLimit,trailingStop(ratchets favorably),bracket(entry + take-profit + stop-loss, OCO-linked, reduce-only),ctx.oco(a, b), time-in-force viaexpiresAfterBars,cancel/cancelAll. - Sizing —
ctx.size.percentOfEquity(pct)andctx.size.riskPerTrade(riskPct, stopPrice)return quantities; you pass them to order calls. - Session —
ctx.session.dayOfWeek(),timeOfDayMs(),isFirstBarOfDay(),isLastBarOfDay()(pure UTC;isLastBarOfDaypeeks at the next bar's timestamp only — documented, deliberate, so you can flatten before close). - State —
ctx.position,ctx.equity,ctx.cash,ctx.orders,ctx.bars,ctx.params,ctx.log(...),ctx.random()(seeded — runs are reproducible).
See the Strategy API reference for the full member list and every field's exact semantics.
Parameters and sweeps
A strategy declares its own parameters in config.params (as in the example above); Configure's
Optimization group renders one row per declared parameter, seeded with the strategy's
defaults. Names are read-only there — add or rename a parameter by editing the strategy source —
but each row's sweep mode (fixed / list / range, with from/to/step) is set right in that row. The
run count preview updates live — every param combination × every symbol becomes a run.
- Grid vs. random search — grids explode fast; random search samples N combos from the grid (set with Samples). Use random when the preview count gets large.
- Early stopping — Early stop aborts the sweep after N runs without leaderboard improvement (0 = off).
- Results — sortable leaderboard (pick your columns; they persist), a threshold filter on any metric, and a param heatmap showing how a chosen metric varies across two chosen parameter axes. A strategy whose good region is one isolated cell is curve-fit; a broad plateau is a real edge.