Warm-up Periods
Indicators like EMA, RSI, and ATR need a minimum number of bars before they produce valid output. The warm-up period prevents your strategy from trading on incomplete data.
The Problem
On the first few bars, indicators return NaN because they haven't accumulated enough data:
Bar 0: EMA(20) = NaN Bar 1: EMA(20) = NaN ... Bar 19: EMA(20) = NaN Bar 20: EMA(20) = 48523.45 (first valid value)
If your strategy doesn't handle this, it will make decisions based on invalid data.
How warmupBars Works
The warmupBars parameter tells the engine to skip the first N bars of onBar() execution entirely. Your strategy code simply won't run until bar N.
// In your strategy parameters { warmupBars: 50 // Skip the first 50 bars }
The engine starts the onBar loop at bar warmupBars instead of bar 0. This is more efficient than checking q.isNaN() on every bar — the engine skips the loop iterations entirely.
Calculating the Right Value
Your warm-up should be at least as long as the longest indicator period, plus a safety margin:
| Indicator | Period | Warm-up Needed |
|---|---|---|
| EMA(20) | 20 | 20 bars |
| RSI(14) | 14 | 15 bars |
| BBANDS(20) | 20 | 20 bars |
| MACD(12,26,9) | 26+9 | 35 bars |
| ATR(14) | 14 | 15 bars |
Rule of thumb: Take the largest indicator period and add 1.
// If your strategy uses EMA(50) and RSI(14): // Warm-up = max(50, 14) + 1 = 51 { warmupBars: 51 }
Defensive Coding: q.isNaN()
Even with warmupBars set, it's good practice to guard against NaN values. This protects against edge cases and makes your strategy robust:
function onBar(ctx, i) { const ema = ctx.ind.ema[i]; const rsi = ctx.ind.rsi[i]; // Early return if any indicator isn't ready if (q.isNaN(ema) || q.isNaN(rsi)) return; // Safe to use indicators here if (rsi < 30 && q.crossOver(ctx.series.close, ctx.ind.ema, i)) { ctx.order.market('ASSET', 1, { signal: 'buy' }); } }
Chained Indicators
When one indicator depends on another's output, warm-up compounds:
function init(ctx) { ctx.addIndicator('ema', 'EMA', { period: 50 }); ctx.addIndicator('rsi', 'RSI', { period: 14 }); // If you use EMA of RSI, you'd need 50 + 14 = 64 bars }
Impact on Backtests
- Warm-up bars consume data but don't generate trades
- A 200-bar warm-up on 500 bars of data leaves only 300 bars for actual trading
- Shorter timeframes (1h) provide more bars, so warm-up is less impactful
- Longer timeframes (1d) with large warm-ups may leave too few bars — use adequate date ranges
Common Mistakes
1. Setting warmupBars too low
// Wrong: EMA(200) needs at least 200 bars { warmupBars: 50 } // Correct { warmupBars: 201 }
2. Not accounting for all indicators
// Uses EMA(20), RSI(14), and BBANDS(50) // warmupBars should be max(20, 14, 50) + 1 = 51
3. Forgetting compound indicators MACD uses a 26-period EMA and a 9-period signal line, so it needs at least 35 bars.
Related
- init() — Declaring indicators
- q.isNaN() — Checking indicator readiness
- Indicator Computation — How the engine computes indicators