A fully vectorized backtest of a daily 09:20 BankNifty short-strangle strategy over one year of minute-level option data — ~9.4M bars processed in ~18 seconds with no per-row loops. It is gross-positive, but the edge lives almost entirely on expiry day, and realistic costs likely erase it at one-lot scale. The point of the project is the method and the verdict, not a headline return.
A short strangle sells a call and a put at once, collecting two premiums and profiting when the index stays range-bound and the options decay. The whole rulebook fits in three lines.
Among all calls, pick the one whose 09:20 one-minute close is nearest ₹50; do the same for puts. Short one lot (15) of each. The ₹50 target is a model-free proxy for constant moneyness-in-vol-units.
Buy a leg back if its price hits 1.5 × entry (a 50% loss on a short), watched on each bar's High — the only in-bar evidence of the intrabar maximum.
Any leg not stopped out is bought back at the 15:20 close. On expiry day this is usually pennies; the position is always flat overnight.
Losses cap at half the premium; expiry-day winners decay toward the full premium. Breakeven win rate is ℓ/(w+ℓ) ≈ 1/3 — the strategy can lose more often than it wins and still make money.
Headline figures on a fixed one-lot book with a ₹100,000 accounting base. Every number below is reproducible from the source; nothing is tuned after the fact.
| Metric | Value | Reading |
|---|---|---|
| Gross P&L | ₹8,259 | on 1 lot (15 qty), no compounding |
| CAGR | 8.27% | on the accounting base — a convention, not return-on-margin |
| Max drawdown | −3.11% | shallow: per-leg losses are stop-capped |
| Sharpe (gross) | ≈1.21 | daily mean ₹33.4 / sd ₹433.4 |
| Win rate | 49.6% | 245 winners / 249 losers |
| Exit mix | 244 / 250 | stop-loss / time exits |
| Est. annual costs | ₹12–25k | brokerage + STT + exchange/SEBI/stamp |
The asymmetry is exactly what theory predicts: theta decay accelerates as time-to-expiry
→ 0 (Θ ∝ τ−1/2),
so the edge concentrates on the day the options expire.
Gross-positive, almost certainly net-negative at one-lot scale. Estimated annual costs (₹12–25k) exceed the ₹8,259 gross profit, and the entire surviving economic core is expiry-day theta. So as specified, this is an existence proof of an expiry-day variance premium — not a deployable system.
The disciplined thing to do next is not to deploy it but to test it: a cost-inclusive re-run, a parameter-sensitivity sweep to confirm the result is a plateau rather than a lucky point, and an out-of-sample year in a different volatility regime.
Nine-plus million rows means per-row Python loops are off the table. Every stage is a whole-table operation that runs in compiled C.
A single groupby(["Date","Type"]).idxmin() on premium-distance picks the nearest-₹50 option per day and side.
Mask High ≥ 1.5·entry, sort, groupby.first() — the earliest in-window touch, τ* = inf{t > t₀ : Hₜ ≥ 1.5Pₑ}.
Dates and tickers are parsed on unique values and broadcast back; pyarrow CSV engine with a pandas fallback.
Auto-detects two spot schemas; drops exact duplicate rows but treats price-conflicting keys as a fatal error, never a silent keep-first.
The interesting part of any backtest is what it admits it isn't handling. Stated plainly.
| Concern | Status | How, or why not |
|---|---|---|
| Look-ahead bias | ✓ handled | Selection uses only the 09:20 close; the stop scan starts the bar after entry (that bar's own High is pre-entry information). |
| Data leakage | ✓ handled | No future bar informs any decision; the breach is the earliest in-window touch only. |
| Survivorship | ✓ mostly | Daily universe is whatever was quoted at 09:20; no hindsight liquidity filter; last-bar fallback avoids dropping thin legs. |
| Transaction costs | ✗ excluded | Per spec — but quantified (₹12–25k/yr) and shown likely to exceed gross P&L. |
| Slippage | ✗ excluded | Stops fill at the exact level; the direction of the bias (optimistic) is documented. |
| Rebalancing | — n/a | Intraday, flat overnight, fixed daily size — nothing to rebalance. |
| Corporate actions | — n/a | Cash-settled index options; no splits or dividends on the index level. |
One more caveat worth stating: the specification fixes Wednesday as expiry day, but real BankNifty weeklies expired on Thursdays until September 2023 — so the expiry flag is mislabelled for part of the year. A production version needs a date-keyed expiry calendar. Full treatment, including the derivations and a production-upgrade path, is in the methodology write-up.