Six Backtesting Mistakes That Silently Destroy Indian Retail Trading Strategies

Why your 2.0 Sharpe strategy loses money live. The six specific biases that inflate retail backtest results — and the code patterns to avoid each.

Six Backtesting Mistakes That Silently Destroy Indian Retail Trading Strategies

The pattern. A retail trader backtests a strategy. The backtest shows a 2.0 Sharpe ratio and a smooth equity curve. They deploy live. Six weeks later the strategy is flat or losing. They blame "changing market conditions". The actual cause, 80% of the time, is one or more of six backtesting mistakes that inflated the backtest and weren't caught before deployment.

This article walks through each mistake with code-level detail so you can audit your own backtests before trusting them with real capital.

Mistake 1 — Look-ahead bias

What it is. Your strategy uses information at time T that you couldn't actually have known at time T.

Where it hides in retail pandas code. The single most common form: using signal and return on the same bar. If your signal is "buy when EMA_fast crosses EMA_slow", you observe that crossover at the end of bar N. You can only act at the open of bar N+1. But naive code does:

data['signal'] = (data['ema_fast'] > data['ema_slow']).astype(int)
data['return'] = data['Close'].pct_change()
data['strategy'] = data['signal'] * data['return']  # WRONG — look-ahead

The fix. Shift the position by one bar:

data['position'] = data['signal'].shift(1)
data['strategy'] = data['position'] * data['return']  # correct

The single .shift(1) line can cut your apparent Sharpe by 40-60%. That is the backtest telling you the truth.

Mistake 2 — Survivorship bias

What it is. You backtest on today's surviving stocks instead of the stocks that existed during the backtest window.

Why it inflates returns. Stocks that went bankrupt, were delisted, or fell out of your universe are not in your dataset. The remaining stocks are, by definition, the survivors — they had better outcomes. Your backtest benefits from their survival without paying for the loss of the non-survivors.

Indian-market impact. In the Nifty 500 universe, roughly 30-50 stocks are added and dropped every year. Over a 10-year backtest, that's 300-500 instrument-year churns. A retail backtest on today's Nifty 500 constituents alone would miss ~40% of the adverse outcomes.

The fix. Use a survivorship-bias-free dataset. Kite Connect's historical data API provides one. For free alternatives, yfinance mostly works for currently-listed stocks but is thin on delisted names. For serious work, subscribe to NSE's own historical data feed or use a vendor like Refinitiv.

Mistake 3 — Data-snooping / multiple testing

What it is. You test 100 parameter combinations and report the best one. The best combination's performance includes both the real edge and the noise.

Why it fools retail traders. A strategy that backtests at 2.0 Sharpe on fast=20, slow=50 might drop to 0.4 Sharpe on fast=18, slow=48 — and to -0.3 on fast=25, slow=60. That's not robust edge; that's noise plus one lucky parameter combination.

The fix. Run parameter sweeps, but report the Sharpe distribution across the sweep, not the peak. Apply Bonferroni correction for multiple testing: if you tested 100 combinations at α=0.05, the real threshold per combination is 0.0005. Visualise the heatmap; a robust strategy shows a plateau, an overfit one shows a single-cell peak surrounded by cliffs.

The 10-line robustness test:

import pandas as pd
import numpy as np
results = []
for fast in range(5, 50, 5):
    for slow in range(50, 200, 10):
        sharpe = run_backtest(data, fast, slow)
        results.append({'fast': fast, 'slow': slow, 'sharpe': sharpe})
heatmap = pd.DataFrame(results).pivot(index='fast', columns='slow', values='sharpe')
print(heatmap)

If the neighbouring cells of your best cell drop by >50%, you have overfitting.

Mistake 4 — Under-counted transaction costs

What it is. Your backtest assumes zero or unrealistic trading costs. Production trading is expensive.

Realistic Indian equity delivery round-trip costs:

ComponentBuy sideSell side
STT0.1%0.1%
Brokerage (Zerodha CNC)₹0 or 0.03%₹0 or 0.03%
Exchange turnover0.003%0.003%
Stamp duty0.015%0
SEBI fee0.0001%0.0001%
GST on brokerage + exch18% of above18% of above

Round-trip total: ~0.30-0.40%. For intraday: ~0.10-0.20%. For F&O: ~0.05-0.20% depending on instrument.

The fix. Build costs into every trade. The minimal correct pattern:

data['trade'] = data['position'].diff().abs()
data['cost'] = data['trade'] * 0.0035  # 0.35% round trip
data['net_strategy'] = data['strategy'] - data['cost']

A strategy that shows 18% gross annualised return with a 100-trade-per-year turnover loses 35% of gross returns to costs. Net annualised: 12%. If you didn't model costs, you thought you had 18%; live reality delivers 12%.

Mistake 5 — Cherry-picked start and end dates

What it is. Your backtest window begins at a market low and ends near a market high, or vice versa. The strategy performance is heavily driven by the framing, not the strategy's edge.

The fix. Test on multiple non-overlapping windows. Specifically:

  1. Walk-forward validation. Train on 2015-2018, test on 2019. Train on 2015-2019, test on 2020. Train on 2015-2020, test on 2021. Each test window is truly out-of-sample.
  2. Regime-segmented tests. Separately evaluate performance during trending regimes (ADX > 25) vs ranging (ADX < 20). A strategy that works only in one regime is fragile.
  3. Bootstrap resampling. Resample the trade sequence with replacement 10,000 times; observe the distribution of outcomes. Real edge holds up; luck collapses.

Mistake 6 — Ignoring the paper-to-live bridge

What it is. The backtest is good. The live trading is worse. The gap is execution: slippage, order rejections, partial fills, latency, missed ticks.

Why retail traders miss it. A backtest simulates perfect fills at the bar's closing price. Live execution pays a spread, eats market impact, and occasionally gets rejected by the broker's OMS.

The realistic gap. For an Indian retail intraday strategy at modest size (say, 10-20 lakh deployed), expect live performance to underperform backtest by 20-40% even after modelled costs. At institutional size, the gap widens.

The fix. 30+ paper-traded trades on the exact same infrastructure you'll use live. No shortcuts — paper in the production system, not in a separate sandbox. Reconcile paper-trade PnL against the backtest projection; the gap is your real-world execution degradation.

The professional audit checklist

Before trusting any backtest with real capital:

  1. .shift(1) on position applied everywhere → look-ahead bias addressed
  2. Survivorship-bias-free dataset → delisted names included
  3. Parameter-sweep heatmap → robust plateau not cliff
  4. Realistic Indian transaction costs modelled → total ~0.30-0.40% round-trip
  5. Walk-forward or regime-segmented validation → no cherry-picked window
  6. Paper-trade bridge completed → live execution gap measured

Passing all six is the minimum for deployment. Any one missing is a known cause of the retail backtest-to-live gap.

How Bharath Shiksha fits

Stage 4 Volume 2 (Backtesting Foundations) walks through each of these mistakes with working pandas code and the exact fixes. Stage 4 Volume 5 (Advanced Validation) covers the statistical tools (PBO — Probability of Backtest Overfitting; DSR — Deflated Sharpe Ratio; Combinatorial Purged Cross-Validation) that catch overfit strategies before they reach production.

The 30-volume curriculum is at bharathshiksha.com. Stage 4 alone is ₹12,999; the full stack bundle with all prerequisites is ₹39,999.


Educational only. Backtesting is not predictive of live trading outcomes. Trading involves risk of capital loss.

Related reading

Ready to go deeper than this article?

Bharath Shiksha is a 30-volume curriculum across 6 stages — from chart reading (Stage 1 at ₹2,999) through capital raising (Stage 6 at ₹18,999), or the full bundle at ₹39,999. Every volume has a 14-page companion worksheet, a 10-question gate quiz, and a 7-day money-back guarantee.

See the full curriculum →