Finally, the part that is ours. The futures stack is mostly the same as any trading system — but there is one trap unique to futures that quietly breaks most beginners’ backtests, so we spend real time on it.
The stack
The components are familiar:
- Market data — OHLC, market depth, open interest, per-contract prices and the underlying spot. From your broker’s API.
- Broker API — in India, Zerodha Kite Connect, Upstox, Angel One SmartAPI and others cover both NSE (index/stock futures) and MCX (commodities) with REST + WebSocket.
- Your logic — the signal (trend, breakout, spread), made roll-aware, plus risk checks.
- Orders — place, modify, attach stops, square off, and roll near expiry.
- Monitor — positions, MTM, margin and liquidation buffer, open interest — with a kill-switch.
The trap: continuous contracts
Here is the thing that catches everyone. For backtesting you want one long, continuous price series for, say, “Nifty futures” going back years. But there is no such instrument — each month is a separate contract that is born, lives a month, and expires. If you naively glue the monthly contracts end-to-end, the price jumps at every roll by the contango/backwardation gap (Lesson 6), and your backtest invents fake profits or losses at each join. A trend system might show a beautiful equity curve that is entirely an artefact of roll gaps.
The fix is back-adjustment: when you roll from one contract to the next, shift all the older data by the roll gap, so the stitched series is continuous (no jump at the join). The shape of the series is preserved; only the absolute level is adjusted. Then returns computed on it are real.
def back_adjusted_series(contracts):
"""contracts: list of (dates, prices) per month, oldest first, with roll points.
Shift older data so the series is continuous at each roll (no fake jump)."""
series = contracts[-1][:] # newest contract as the anchor
offset = 0.0
for older, newer in reversed(list(zip(contracts, contracts[1:]))):
gap = newer.price_at_roll - older.price_at_roll # the roll jump
offset += gap
series = [(d, p + offset) for d, p in older] + series
return series
# without this, every futures backtest you run is quietly brokenYou can also buy pre-built continuous contracts from data vendors, but understand the adjustment either way — there are different conventions (back-adjusted, ratio-adjusted), and they affect your results.
Model the real costs
Beyond the continuous-contract trap, a futures backtest must include what live trading will charge you:
- Fees and taxes — brokerage, exchange fees, and STT/CTT. On frequent strategies these dominate.
- Roll yield — the contango bleed or backwardation gain (Lesson 6). Ignoring it can flip a winning strategy to a loser.
- Slippage — you won’t always get the backtest’s fill, especially in size or in less-liquid commodities.
A strategy that looks great before costs often loses after them — so always model them, and then paper-trade forward before risking real money.
Pitfalls
- Backtesting on naively-stitched contracts. The roll gaps create fake P&L. Back-adjust, always.
- Ignoring roll yield and STT/CTT. Both are real drags that can turn a “winner” into a loser. Model every cost.
- No kill-switch / margin monitor. Futures are leveraged and marked-to-market daily; a bug or a gap can bleed cash fast. Monitor margin live and have a tested flatten-everything button.
Recap
The futures stack — data → broker API → logic → orders → monitor — is the usual one, and Indian brokers cover NSE + MCX. The trap unique to futures is the continuous contract: stitch months into one series with back-adjustment (shift older data by the roll gap) or your backtest invents fake P&L. Also model fees, STT/CTT, roll yield and slippage, monitor margin live, and keep a kill-switch. Build small, paper-trade, go live tiny.
Next: Lesson 12 — Build a Futures Algo, assembling everything into one end-to-end trend-following system.