Skip to content
Back to skills

Backtest Lab

ASecurity

Backtests rule-based swing ideas on NSE/BSE daily bars with Indian frictions built in: decide on the close and fill at the next open, gap-through stops, slippage, exact per-trade charges by segment and broker (STT, stamp duty, GST, DP), whole shares or lots, circuit-band no-fills, futures-only shorts and an optional STCG/LTCG estimate. Six built-in strategies; CAGR vs buy-and-hold, drawdown, Sharpe/Sortino, profit factor, expectancy in ₹ and R; parameter sweeps with a plateau-or-spike read, w...

  • 49 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
businesspythongobashapi

Works with

  • api
  • mcp

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned November 7, 2026

npx -y skills add Shubhamkumartrichy/claude-trading-skills-india --skill backtest-lab --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Backtest Lab?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Backtest Lab
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/shubhamkumartrichy-backtest-lab/badge)](https://www.skillsdirectory.com/skills/shubhamkumartrichy-backtest-lab)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: backtest-lab
description: |
  Backtests rule-based swing ideas on NSE/BSE daily bars with Indian frictions built in: decide on
  the close and fill at the next open, gap-through stops, slippage, exact per-trade charges by
  segment and broker (STT, stamp duty, GST, DP), whole shares or lots, circuit-band no-fills,
  futures-only shorts and an optional STCG/LTCG estimate. Six built-in strategies; CAGR vs
  buy-and-hold, drawdown, Sharpe/Sortino, profit factor, expectancy in ₹ and R; parameter sweeps
  with a plateau-or-spike read, walk-forward and Monte Carlo. backtest.py, stdlib only, offline.

  Use when the user asks: "backtest this strategy", "would a 20/50 crossover have worked on X",
  "is this edge real or curve-fit", "optimise my parameters", "walk-forward test", "Monte Carlo my
  trades", "what drawdown should I expect", "do charges kill this idea", "does a NIFTY 200-DMA
  filter help", or before putting real money behind any mechanical rule.
---

# Backtest Lab

A backtest cannot prove an idea works. It can show quickly that an idea does not. Judge a rule set
by how little it degrades when Indian charges, overnight gaps, unseen years and bad luck are piled
on, and treat a spectacular first run as a reason for suspicion.

## Commands

```bash
python3 scripts/backtest.py strategies                                  # rules and defaults
python3 scripts/backtest.py run --csv tcs.csv --strategy sma-cross --params fast=20 slow=50
python3 scripts/backtest.py run --symbol POLYCAB --strategy 52w-high --risk-pct 1 --tax --trades-csv t.csv
python3 scripts/backtest.py run --csv hdfcbank.csv --strategy donchian-breakout --regime-csv nifty.csv
python3 scripts/backtest.py run --csv nifty.csv --strategy sma-cross --segment futures --lot-size 65 \
    --allow-short --leverage 3 --size-pct 300
python3 scripts/backtest.py sweep --csv tcs.csv --strategy sma-cross --grid fast=10,20,30 slow=50,100,200
python3 scripts/backtest.py walkforward --csv tcs.csv --strategy donchian-breakout \
    --grid entry_n=20,40,55 exit_n=10,20 --is-bars 756 --oos-bars 252
python3 scripts/backtest.py montecarlo --csv tcs.csv --strategy ema-pullback --sims 2000 --seed 7
python3 scripts/backtest.py montecarlo --from-trades t.csv --ruin-dd 25
```
Inputs follow `technical-analysis`: `--csv`, `--candles` (broker-MCP JSON, null candles skipped) or
`--symbol` (yfinance, `--period 10y`). `--start` sets the first trading day while earlier bars still
warm up the indicators. `--grid` also takes ranges (`fast=10:50:10`). Every command takes `--json`.
Bad input exits 2 with one `error:` line.

## Strategies (`--strategy`, override with `--params key=value`)

| Name | Rule (long side; shorts mirror it) | Defaults |
|---|---|---|
| sma-cross | fast SMA crosses above slow; out when back below | fast 20, slow 50, stop 3×ATR |
| ema-pullback | close above a rising trend EMA, low tags the EMA, close back above it | ema 20, trend 100, 2×ATR, target 2R, 20 bars |
| **rsi-reversion** | RSI(period) below buy_below while above the trend SMA; out above sell_above | 2, 10, 70, trend 200, 10 bars |
| donchian-breakout | close above the prior entry_n high; out below the prior exit_n low | 20 / 10, 2×ATR |
| bollinger-reversion | close below the lower band; out at the middle band | 20, k 2, trend 0 (off), 20 bars |
| 52w-high | close above the prior lookback-bar high; out below the exit SMA | 252, exit_ma 50, 2.5×ATR |

Every strategy also takes `stop_atr` (k × ATR14 from the fill), `stop_pct` (overrides it),
`target_r` and `max_hold` (bars), where 0 turns each one off. There is no custom-code hook: new
rules are added to the script as reviewed code.

## How orders and costs are simulated

- **No look-ahead.** Signals use bars up to and including the signal bar's close. The order fills at
  the next bar's open, never at the close that produced it.
- **Stops and targets** rest in the market. A gap through the level fills at the open (a stop at ₹101
  after a ₹96 open books ₹96). If one bar touches both, the stop is assumed to have filled first.
- **Slippage** `--slippage-bps` (default 5) on every fill. Use 15–30 for small caps.
- **Charges** come from `trading-costs` (charges.py) for each round trip, by `--segment
  delivery|intraday|futures` and `--broker`. A delivery buy sold the same day is charged as intraday.
- **Sizing**: `--size-pct` (default 100% of equity), `--risk-pct` (risk to the initial stop, the
  `position-sizing` method) or `--fixed-inr`. The size is set on the signal close, capped by the cash
  available at the fill, and rounded down to whole shares or whole `--lot-size` lots (futures, SME).
- **Intraday** turns each signal into a next-session open-to-close MIS trade.
- **Shorts** need `--segment futures --allow-short`. An Indian cash-market short has to be bought
  back the same session (SLB aside), so a multi-day short only exists as futures. Futures notional ≤
  equity × `--leverage`. Rollover, basis and margin calls are not modelled.
- **Circuits**: with `--circuit-band 2|5|10|20`, an open at the band limit gets no fill. The entry is
  skipped; an exit or stop waits for the next unlocked open. Each case is listed under order events.
- **Regime filter**: `--regime-csv nifty.csv` (or `--regime-symbol NIFTY`) allows longs only while the
  benchmark closes above its `--regime-ma` (200) SMA on the signal day, and shorts only below it.
- **Cash** earns nothing. Sharpe and Sortino subtract `--rf` (default 6.5% a year, about a liquid
  fund or T-bill; change it).
- **`--tax`** applies `indian-tax-for-traders` rates for a rough post-tax view: STCG 20% (held ≤12
  months), LTCG 12.5% above ₹1.25 lakh per FY, intraday and futures at `--slab-pct`, plus 4% cess.
  It is approximate: losses are not carried forward and STT is treated as deductible, which it is not.

## Reading the output

- **Versus buy-and-hold** (delivery, one round trip). A rule earns its keep with a better Sharpe or
  Calmar, or a much smaller drawdown, not just a higher return in a bull market.
- **Max drawdown and longest underwater stretch**: would you have kept following the rules through it?
- **Profit factor** (after charges) below 1.2 is thin. Above 2 on fewer than 30 trades is suspicious.
- **Expectancy**: about +0.2R or more after costs is worth a closer look. Breakeven trades (net ₹0)
  count as neither wins nor losses and do not end a losing streak.
- **Charges as % of gross profit** above 25% means costs, not the market, own the result.
- **Yearly table**: one great year carrying the whole test is regime luck.
- **Sweep**: *plateau* (neighbours keep ≥75% of the best score) is what you want. *Slope* means pick
  the middle of the good zone. *Spike* means curve-fit. The luck hurdle is the Sharpe the best of N
  zero-edge variants would show by chance; the best cell should clear it comfortably.
- **Walk-forward**: efficiency = out-of-sample CAGR ÷ mean in-sample CAGR. ≥0.5 holds up; below 0
  fails. Frequent parameter changes mean the "optimum" is noise.
- **Monte Carlo**: size for the 95th-percentile drawdown, not the backtest's one path. *bootstrap*
  varies the outcome; *shuffle* only reorders the trades (final equity is fixed, the path is not).

## Checklist: try to break the idea before it breaks your account

1. **Look-ahead**: can every input be known before the order? Today's high/low or close cannot
   trigger an entry today. Dividend-adjusted closes are not prices anyone traded at.
2. **Survivorship**: today's NIFTY 50 or a current screener list are winners by construction. Ten
   years on them flatters every long strategy. Add names that were dropped, delisted or merged, or
   test the index itself.
3. **Overfitting**: each extra grid cell is another lottery ticket. Keep grids coarse, insist on a
   plateau, walk forward, and leave the last 1–2 years untouched until the very end.
4. **Costs**: a delivery round trip at Zerodha costs about 0.24% of the position (STT 0.1% a side,
   stamp duty, DP charge). 100 full-size round trips a year is about 24% of capital before any edge.
   High-turnover ideas die here. Rerun at 2–3× slippage and on a costlier broker.
5. **Regime**: does it only work in 2020–21-style rallies? Check the yearly rows, add `--regime-csv`,
   and include a bad phase (2008, 2015–16, 2018 small caps, March 2020, late 2024–early 2025).
6. **Data**: Yahoo history usually adjusts splits and bonuses, but not always. Moves of 25% or more
   overnight are flagged; check them. Dividends are left out for both the strategy and buy-and-hold.
7. **Small samples**: under 30 trades is an anecdote. Run the same rules on several stocks and look
   at the spread of results, not the best one.
8. **Execution**: thin names lock at circuits and slip more (`--circuit-band`, higher slippage). Stop
   orders are DAY-only on exchanges, so a real system needs GTTs (see `breakout-planner`).

## Workflow with other skills

1. `market-regime` (../market-regime/scripts/regime.py): test whether a NIFTY trend filter
   (`--regime-csv`) cuts drawdown more than it cuts return before using it live.
2. Run `run` → `sweep` → `walkforward` → `montecarlo`, and stop at the first red flag.
3. `position-sizing` (../position-sizing/scripts/risk.py): choose a `--risk-pct` that keeps the Monte
   Carlo 95th-percentile drawdown survivable.
4. `trading-discipline`: `--trades-csv` writes journal columns, so
   `python3 ../trading-discipline/scripts/discipline.py review t.csv` breaks the backtest down by
   weekday and setup. Compare it with your live journal later; a large gap is a warning.
5. `breakout-planner`: 52w-high and donchian-breakout are rough stand-ins for its Stage-2 entries.
   They tell you whether breakout buying pays on a stock at all, not whether a given VCP will work.

Limits: one symbol and one position at a time (no portfolio rotation), daily or weekly bars, no
options, no tick rounding.

## Output

```
NAME: strategy (params) | segment on NSE via broker | capital | sizing | slippage
start → end | bars (years) | signal on close, fill next open | charges as of <date>
Strategy vs Buy & hold: total return, CAGR, max DD, volatility, Sharpe (rf), Sortino, Calmar
Trades N: W / L / BE | win rate | avg win / loss | PF | expectancy ₹ (R) | payoff | losing streak
Exposure | avg hold | max DD dates | longest underwater | charges (% of gross profit) | slippage
Year table | last trades | order events | red flags | data notes
```

_Educational analysis, not investment advice. A backtest describes the past, not your next trade._

Files in this skill

  • SKILL.md10.3 KB
  • scripts/backtest.py84.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.