onBar()
Main strategy hook called for each candle in the dataset.
Signature
function onBar(ctx, i)
Parameters
| Name | Type | Description |
|---|---|---|
ctx | BarContext | Context with indicators, price data, orders |
i | number | Current bar index (0-based) |
Execution Timing
- Called after each bar closes
- Runs for every candle in the backtest range (i = 0 to N-1)
- Orders placed here execute on the next bar's open
Available Context
function onBar(ctx, i) { // Parameters (from define) ctx.p.fastLength // Your parameters // Indicators (from init, pre-computed) ctx.ind.ema[i] // Single-output indicator ctx.ind.macd.macd[i] // Multi-output indicator // Price series ctx.series.open[i] ctx.series.high[i] ctx.series.low[i] ctx.series.close[i] ctx.series.volume[i] // Position info ctx.position('ASSET') // Returns { qty, avgPrice, side } // Order placement ctx.order.market(...) ctx.order.close(...) ctx.order.reduce(...) // Mutable state ctx.state.myValue }
Example
function onBar(ctx, i) { const fast = ctx.ind.fastEma[i]; const slow = ctx.ind.slowEma[i]; // Skip if indicators not ready (warm-up period) if (q.isNaN(fast) || q.isNaN(slow)) return; const pos = ctx.position('ASSET'); // Entry: bullish crossover if (q.crossOver(ctx.ind.fastEma, ctx.ind.slowEma, i) && pos.qty === 0) { ctx.order.market('ASSET', 1, { signal: 'buy', reason: 'ema_cross' }); } // Exit: bearish crossover if (q.crossUnder(ctx.ind.fastEma, ctx.ind.slowEma, i) && pos.qty > 0) { ctx.order.close('ASSET', { signal: 'sell', reason: 'ema_cross' }); } }
Order Timing
Critical concept: Orders placed in onBar do not fill immediately.
Bar i closes → onBar(ctx, i) runs → Order queued Bar i+1 opens → Order fills at open price (± slippage)
This models realistic execution where you cannot act within a closed bar.
Common Patterns
Check indicator readiness
function onBar(ctx, i) { // Indicators have warm-up periods if (q.isNaN(ctx.ind.ema[i])) return; // Safe to use indicators now }
Position-aware logic
function onBar(ctx, i) { const pos = ctx.position('ASSET'); if (pos.qty === 0) { // Flat - look for entries } else if (pos.qty > 0) { // Long - manage position } else if (pos.qty < 0) { // Short - manage position } }
Using previous bar data
function onBar(ctx, i) { if (i < 1) return; // Need at least 2 bars const prevClose = ctx.series.close[i - 1]; const currClose = ctx.series.close[i]; if (currClose > prevClose) { // Price went up } }
Common Mistakes
Expecting immediate fills
// WRONG - Position unchanged until next bar function onBar(ctx, i) { ctx.order.market('ASSET', 1); const pos = ctx.position('ASSET'); // Still 0! }
Accessing future data
// WRONG - i+1 may not exist yet function onBar(ctx, i) { const tomorrow = ctx.series.close[i + 1]; // undefined or error }
Forgetting warm-up check
// WRONG - ema[i] is NaN for first N bars function onBar(ctx, i) { if (ctx.ind.ema[i] > ctx.series.close[i]) { // NaN comparison! } // RIGHT function onBar(ctx, i) { if (q.isNaN(ctx.ind.ema[i])) return; // Now safe }
Related
- init() — Indicator declaration
- Order API — Placing orders
- Helper Functions — q.* utilities
lifecycleonBartradinglogicqsl