import pandas as pd,numpy as np,datetime as dt,json,html L='/workspace/research/popular-strategies'; R=lambda f:pd.read_csv(f'{L}/results/{f}') now=dt.datetime.now(dt.timezone(dt.timedelta(hours=5,minutes=30))).strftime('%Y-%m-%d %H:%M IST') def tb(df,fl=2): return df.to_html(index=False,float_format=lambda x:f'{x:,.{fl}f}',border=0,na_rep='–',escape=True) css=open('/workspace/research/flow-forensics-2026-10/index.html').read().split('')[0] NAMES={'P1':'ORB15 breakout','P2':'ORB5 first 5-min candle','P3':'Prior-day high/low breakout','P4':'Narrow-CPR breakout + VWAP','P5':'Supertrend(10,3) flip + VWAP','P6':'EMA 9/21 cross + VWAP','P7':'RSI(14) 30/70 reversal','P8':'Gap fade (gap-fill)'} RULE={'P1':'First 5m close beyond 09:15–09:29 range (09:30–12:00). Stop = other side of the range.', 'P2':'First 5m close beyond the 09:15–09:19 candle (to 11:00). Stop = other side.', 'P3':'Day opens inside the prior-day range; first 5m close above PDH / below PDL (to 14:00). Stop = breakout bar extreme ±5.', 'P4':'Narrow-CPR day only (width < 30th pct of last 250 days). First 5m close beyond CPR and on the same side of VWAP (09:30–12:00). Stop = other CPR edge.', 'P5':'5m Supertrend(10,3) flips, confirmed by VWAP side (09:45–14:30). Stop = Supertrend line.', 'P6':'5m EMA9 crosses EMA21, confirmed by VWAP side (09:45–14:30). Stop = 6-bar swing ±5.', 'P7':'5m RSI(14) crosses back above 30 (CE) / below 70 (PE) (09:45–14:30). Stop = 6-bar swing ±5.', 'P8':'Gap 0.3–1.5%: first 5m close back through the 15-min range toward the gap fill (to 11:00) → buy against the gap. Stop = other side of range.'} WORKS={'P1':'No stable regime. No filter qualified on the 2024–25 options train. On spot, the walk-forward picked a different label each year (expiry → prior-day range), and the OOS edge was not significant.', 'P2':'Lead: after an NR7 day. Chosen on train (+2.1, n=42). 2026: +28.3 pts (n=29, p=0.025, beats 99% of random subsets), but BH q=0.35. Not proven. On spot, the walk-forward kept picking expiry days, which flipped sign year to year.', 'P3':'Lead: after an NR7 day (2026 +23.0, n=15, p=0.10, q=0.39). On spot, the filter flipped between FII-long and prior-day-range labels. Not proven.', 'P4':'Lead: NR7 / compressed prior day (options 2026 +23.7, n=18; spot 2026 +33, n=28, p=0.009, q=0.30). Only one OOS year exists (no VWAP data before Oct-2024). Not proven.', 'P5':'Mid efficiency-ratio days (0.2–0.4) chosen on train. 2026 +11.9 (n=33, p=0.13). Not proven. The meta-switch hurt.', 'P6':'No. The train-chosen filter (compressed prior day) went negative in 2026 (−3.2).', 'P7':'No. The train-chosen filter (compressed prior day) was −8.0 in 2026, worse than always-on. On spot, no filter qualified in 2025–26.', 'P8':'No. No filter qualified on train. The M20 switch was +26 in 2026, but on only 8 trades.'} P=R('primary_results.csv'); tr=P[P.split=='train'].set_index('cand'); te=P[P.split=='test'].set_index('cand') SY=R('spot_by_year.csv').pivot(index='cand',columns='year',values='spot_net'); PQ=R('positive_quarters.csv').set_index('cand') FM=R('regime_family.csv'); WF=R('spot_walkforward.csv'); MS=R('meta_switch.csv'); RB=R('regime_buckets.csv'); CC=json.load(open(f'{L}/results/cuts_count.json')) N7s=R('nr7_breakouts_spot_by_year.csv'); N7o=R('nr7_breakouts_options_by_year.csv'); E1=R('secondary_E1.csv') rows='' for c in NAMES: sy=' / '.join('–' if pd.isna(SY.loc[c].get(y,np.nan)) else f'{SY.loc[c][y]:+.1f}' for y in [2022,2023,2024,2025,2026]) rows+=f"{c} {NAMES[c]}{tr.loc[c,'n']:.0f} / {tr.loc[c,'mean']:+.1f}{te.loc[c,'n']:.0f} / {te.loc[c,'mean']:+.1f}{te.loc[c,'win']:.0f}%{te.loc[c,'rs_total']:,.0f}{te.loc[c,'random_pctile']:.0f}{te.loc[c,'t_p']:.2f}{te.loc[c,'q_bh']:.2f}{sy}{PQ.loc[c,'spot_q_pos']} · {PQ.loc[c,'opt_q_pos']}{WORKS[c]}" SUM=f"{rows}
StrategyTrain Oct-24–Dec-25
n / net pts per trade
Test 2026
n / net pts
2026 win%2026 ₹ total (1 lot)Pctile vs random entriesp (2026)BH q (8)Spot net pts per trade
2022 / 23 / 24 / 25 / 26
Positive quarters
spot · options
Works in which regime?
" FMv=FM[['test','cand','rule','train_n','train_mean','oos_n','oos_mean','always_mean','p','q_bh','subset_pctile','random_regime_pctile','counts']].rename(columns={'subset_pctile':'pctile_vs_random_subsets','random_regime_pctile':'pctile_vs_random_entries_same_regime','counts':'edge_counts?'}) ms=MS[MS.rule=='M20']; worse=(ms.oos_mean<=ms.always_mean).sum() MSp=MS.pivot_table(index=['data','cand'],columns='rule',values='oos_mean').round(2).reset_index(); MSa=MS.drop_duplicates(['data','cand'])[['data','cand','always_mean']]; MSp=MSp.merge(MSa,on=['data','cand']) po=R('options_by_period.csv'); ps=R('spot_by_period.csv') def pv(df,level): x=df[df.level==level].pivot(index='cand',columns='period',values='mean').round(1); x.columns.name=None; return x.reset_index() RBo=RB[(RB.data=='options')].pivot_table(index=['label','bucket'],columns=['cand','split'],values='mean').round(1) RBo.columns=[f'{a}_{b}' for a,b in RBo.columns]; RBo=RBo.reset_index() # ---- combinations section (s6_combos / s6b) CR=pd.read_csv(f'{L}/results/combo_results.csv'); CSn=pd.read_csv(f'{L}/results/combo_sensitivity.csv').set_index('combo'); CB=pd.read_csv(f'{L}/results/combo_by_period.csv') CDESC={'K2_15':'≥2 strategies, same direction, within 15 min','K3_30':'≥3 strategies, same direction, within 30 min','K2_D':'≥2 strategies, same direction, same day','N2':'NR7 day + P2 (contaminated)','N3':'NR7 day + P3 (contaminated)','N4':'NR7 day + P4 (contaminated)','N234':'NR7 day + ≥2 of P2/P3/P4 (contaminated)','PAIR_BEST':'Best pair on train = P3+P4','PAIR_P1P5':'ORB15 + Supertrend','PAIR_P2P5':'ORB5 + Supertrend','PAIR_P3P4':'PDH/PDL + narrow CPR'} seg=CB[CB.level=='seg'].pivot(index='combo',columns='period',values='mean') ctr=CR[CR.split=='train'].set_index('combo'); cte=CR[CR.split=='test'].set_index('combo'); crow='' for c in CDESC: a,b=ctr.loc[c],cte.loc[c] crow+=f"{c} {CDESC[c]}{a.n:.0f} / {a['mean']:+.1f} / {a.win:.0f}%{b.n:.0f} / {b['mean']:+.1f} / {b.win:.0f}%{b.constituents_mean:+.1f}{b.random_mean:+.1f} ({b.random_pctile:.0f}){b.t_p:.3f}{b.q_bh:.2f}{seg.loc[c,'2026 Jan-Sep']:+.1f}{seg.loc[c,'2026 Oct']:+.1f}{CSn.loc[c,'drop_top3']:+.1f}{b.counts}" CTAB=""+crow+"
ComboTrain n / mean / win2026 n / mean / win2026 mean of its single strategies2026 random mean (pctile)pBH q (11)Jan–Sep 2026Oct 20262026 without best 3 tradesCounts?
" def mx(nm,fl=0): M=pd.read_csv(f'{L}/results/overlap_{nm}.csv',index_col=0); M.index.name='if ↓ fires'; return tb(M.reset_index(),fl) OM=json.load(open(f'{L}/results/overlap_meta.json')) cq=CB[(CB.level=='quarter')].pivot(index='combo',columns='period',values='mean').round(1); cq.columns.name=None; cq=cq.reset_index() csy=pd.read_csv(f'{L}/results/combo_spot_by_year.csv').pivot(index='combo',columns='year',values='mean').round(1); csy.columns.name=None; csy=csy.reset_index() # ---- premium-support section (s7/s8) PP=pd.read_csv(f'{L}/results/prem_primary.csv'); PF=pd.read_csv(f'{L}/results/prem_regime_filters.csv'); PB=pd.read_csv(f'{L}/results/prem_by_period.csv'); PG=pd.read_csv(f'{L}/results/prem_gross.csv').set_index(['var','ex']) VD={'V1':'Prior-day premium low bounce','V2':'Intraday premium swing-low bounce ("black box")','V3':'Round premium level (×25) bounce','V4':'Premium day-high breakout (resistance break)'} XD={'X_OLD':'old: SL20, 40%@+20, 30%@+40, trail rest','X_T50':'structural stop, trail 50% after 2R'} ptr=PP[PP.split=='train'].set_index(['var','ex']); pte=PP[PP.split=='test'].set_index(['var','ex']); prow='' for (v,e),b in pte.iterrows(): a=ptr.loc[(v,e)] prow+=f"{v} {VD[v]}{XD[e]}{a.n:.0f} / {a['mean']:+.1f} / {a.win:.0f}%{b.n:.0f} / {b['mean']:+.1f} / {b.win:.0f}%{PG.loc[(v,e),'test']:+.1f}{b.random_mean:+.1f} ({b.random_pctile:.0f}){b.t_p:.2f}{b.q_bh:.2f}{b.jan_sep:+.1f}{b.oct:+.1f} (n={b.n_oct:.0f}){b.drop_top3:+.1f}{b.counts}" PTAB=""+prow+"
VariantExitTrain n / mean / win2026 n / mean / win2026 mean before charges2026 random mean (pctile)pBH q (8)Jan–Sep 2026Oct 20262026 without best 3Counts?
" PFv=PF[['var','ex','label','buckets','train_n','train_mean','test_n','test_mean','always_on_test','random_same_bucket_mean','subset_pctile','t_p','q_bh','counts']] def pq(ex): z=PB[(PB.level=='quarter')&(PB.ex==ex)].pivot(index='var',columns='period',values='mean').round(1); z.columns.name=None; return z.reset_index() # ---- trader-discretion section (s9/s10/s11) DE=pd.read_csv(f'{L}/results/disc_trader_entries.csv'); DC=pd.read_csv(f'{L}/results/disc_candidates.csv'); DFc=pd.read_csv(f'{L}/results/disc_feature_contrast.csv'); DM=json.load(open(f'{L}/results/disc_model.json')); DH=pd.read_csv(f'{L}/results/disc_filter_fullhist.csv') DE['log_win']=(DE.status_log=='WIN').astype(float) dfam=DE.groupby('family').agg(entries=('family','size'),log_win_pct=('log_win',lambda s:round(s.mean()*100)),replay_win_pct=('replay_win',lambda s:round(s.mean()*100)),xold_nifty_atm_pts=('xold_pts',lambda s:round(s.mean(),1)),covered_by_any_rule_pct=('covered',lambda s:round(s.mean()*100)),covered_by_V_pct=('covered_V',lambda s:round(s.mean()*100))).sort_values('entries',ascending=False).reset_index() dsrc=DC.groupby('src').agg(candidates=('taken','size'),taken=('taken','sum'),take_rate_pct=('taken',lambda s:round(s.mean()*100,1)),xold_mean=('xold',lambda s:round(s.mean(),1)),xt50_mean=('xt50',lambda s:round(s.mean(),1))).reset_index() dts=DC.groupby('taken').agg(n=('taken','size'),xold_mean=('xold',lambda s:round(s.mean(),2)),xold_win_pct=('xold',lambda s:round((s>0).mean()*100)),xt50_mean=('xt50',lambda s:round(s.mean(),2)),xt50_win_pct=('xt50',lambda s:round((s>0).mean()*100))).reset_index(); dts['taken']=dts.taken.map({0:'skipped',1:'taken'}) DRULE=' AND '.join(f"{f} {o} {t:.2f}" for f,o,t in DM['rule']) exec(open('/workspace/research/popular-strategies/scripts/yt_section.py').read()) exec(open('/workspace/research/popular-strategies/scripts/ec_section.py').read()) exec(open('/workspace/research/popular-strategies/scripts/w20_section.py').read()) exec(open('/workspace/research/popular-strategies/scripts/ntv_section.py').read()) H=f"""Popular Nifty option-buying strategies: tested, by period and by regime

8 popular Nifty intraday option-buying strategies: honest backtest, by period and by market regime

Built {now}. Research only, not advice. Read-only data, no orders. Option buyer only (long = buy ATM CE, short = buy ATM PE, nearest weekly with DTE≥1). REAL costs ≈2 pts per round trip. Everything below was pre-declared in PREREG.md (19:20 IST, before any 2026 number). Regime addendum after Satya's 19:17 IST question: “could it be each strategy works for certain periods”.

Short answer.
  1. None of the 8 popular setups is a proven option-buying edge. On the 2024–25 train, all 8 lost money (−2.9 to −19.7 pts per trade). In 2026, 5 of 8 are slightly positive (best: Narrow-CPR +4.8, Supertrend+VWAP +4.4 pts per trade). None is significant: the smallest BH q is {te.q_bh.min():.2f}.
  2. Yes, they do work in some periods and not others, and the main driver is the year itself. The rolling-expectancy and month heatmaps show long on/off stretches. Example: ORB15 on spot was +7.0 in 2024, −5.0 in 2025 and +1.0 in 2026. Only 2–4 of 9 option quarters were positive for any strategy. Much of 2026's improvement comes from October 2026 alone (13 sessions): every strategy was positive that month (+1.7 to +53 pts per trade). For Jan–Sep 2026 only P4 (+1.8), P5 (+3.7) and P7 (+0.4) were positive, and P1 was −5.5. One good month is a period effect, not an edge.
  3. Can you tell the good periods in advance? Mostly no. We screened {CC['n_labels']} ex-ante regime labels ({CC['n_buckets']} buckets: VIX, 20/50-DMA trend, efficiency ratio, prior-day range, NR7, gap size, expiry position, FII index-futures long %), {CC['bucket_results_reported']} bucket results in all. Each strategy's “good regime” was chosen on older data by a fixed rule and applied unchanged to the next period, giving 32 out-of-sample tests. 0 of 32 survive BH (best q=0.30). The “recent-performance switch” (trade only if the last 20 trades were net positive) was no better than always-on in {worse} of 16 cases.
  4. One lead worth watching: breakouts after an NR7 day (yesterday's range was the narrowest of the last 7). It was picked on train, independently, for P2, P3 and P4, and in 2026 it gave +23 to +28 pts per trade (n=15–29, raw p 0.025–0.10). But it is the same ~20–30 days for all three. On spot it helped in 4 of 5 years and reversed in 2024 (−10.6 vs +8.5). It goes on the forward-watch list, not the trade list.
  5. Combining strategies does not raise the odds (Combinations section). The 8 are not independent: when ORB15 fires, ORB5 fires the same way on 89% of those days, and the gap fade is the ORB15 trade 90% of the time. “≥2 agree” and “≥3 agree” did not beat random (2026: −1.3 and +1.3 pts per trade; train −6.6 and −11.0). The best train pair, PDH/PDL breakout + narrow CPR, made +19.8 per trade in 2026 (n=25, p=0.05), but q=0.13 and it falls to +2.8 without its best 3 trades. 0 of 11 combos count.
  6. \n
  7. Premium-support (“black box”) entries on the option chart do not work either (Premium support section). 4 pre-registered variants × 2 exits: all 8 lost money on train and all 8 lost in 2026 (−1.8 to −15.1 pts per trade), most of them worse than random entries at the same minute. They are negative even before charges, so the earlier harsh-cost result was not a cost artefact.
  8. The trader's discretion could not be reverse-engineered (Trader discretion section). All 218 usable log entries were replayed on real 1m bars. The logged 78% win rate becomes 27% on the real bars, and the Black Box calls (14 of 15 logged wins) become 1 of 15. A tree and a logistic on 17 entry-time features did no better than chance on held-out months (AUC 0.46 and 0.55). The pre-registered filter that came out (enter before 09:52 IST) failed the held-out test (lift 0.41) and 2024–26 history (0 of 4 count). The only robust pattern is that he trades early in the day.
  9. The trader's live YouTube calls, scored from the streams, are not an edge either (YouTube live calls section). 58 fresh calls in 31 sessions; 29 scorable on real 1m bars: {YS['mean']:+.1f} net pts per trade, win {YS.win:.0f}%, p={YS.p:.2f}, {YS.drop_top3:+.1f} without the best 3. His self-reported log matches only {NM} of {len(ov)} stream calls in the same window.
  10. Expiry-day cheap (~20) counter-trend options do jump 2×–3× fairly often, but buying them loses (Expiry cheap counter-trend section). Nifty {fn.hit2x:.0f}% / Sensex {fs.hit2x:.0f}% of signals later printed ≥2× and {fn.hit3x:.0f}% / {fs.hit3x:.0f}% ≥3×. A random minute or the with-trend option does about as well, and {fn.near_zero_settle:.0f}% / {fs.near_zero_settle:.0f}% settle at ≈0. All 6 pre-registered buyer tests lose in 2026; 0 count.
  11. The ~20 premium level is not special (Why 20? section). Across levels 5–100, the chance of 2×/3×, of expiring ≈0, and the expectancy all change smoothly; 20 sits on the curve (0 of 18 outlier checks). It does not hold better than 17/18/22/23. “Holds at 20 then breaks out” was +9.3 on Nifty in 2026 but −8.5 on train, p=0.29: noise. 0 of 12 count.
  12. “20 without time value” (ITM by ~20, delta ≈1) has no edge. It is rare: 41 Nifty events in 109 expiries, mostly after 14:30. It expires near zero far less often than the OTM-20 (34% vs 76%), with the same chance of doubling (39% vs 40%), but it is just a late spot bet that also loses before costs. 0 of 18 rows count.

Summary table · Works in which regime? · By period · Rules · Method and limits · Combinations · Premium support · Trader discretion · YouTube live calls · Expiry cheap counter-trend · Why 20? · 20 without time value

1. Summary (primary exit: structural stop, trail 50% of peak after 2R, time exit 15:14; REAL cost)

{SUM}

Percentile vs random: 2026 mean compared with 2000 sets of random-day entries at the same minute, direction and R, with the same exit (50 = no better than random). Spot net = index points minus 1 pt. VWAP-based P4–P6 start Oct-2024 because futures volume is missing before that. Positive quarters: spot 2022–26 · options Q4-2024–Q4-2026.

2. Works in which regime?

How it was tested (pre-declared). Every label is known before 09:15. Rule: on the training window, keep the buckets of one label where the strategy averaged above 0 (with at least 10 option trades or 15 spot trades per bucket). Pick the label with the highest training total. Apply it unchanged to the next period. Options: train Oct-2024–Dec-2025 → test 2026. Spot: expanding walk-forward (train 2022..Y → test Y+1; OOS 2023–2026; the requested 2017 start is impossible because no intraday data before 2022 exists). Meta-switch M20: trade only if the strategy's last 20 completed trades summed above 0. BH over the declared family of 32 tests. A regime edge counts only if q<0.10, the OOS filtered mean beats always-on, and it beats ≥90% of random same-size subsets.

Overfitting disclosure. {CC['n_labels']} labels × {CC['n_buckets']} buckets × 8 strategies × 2 datasets, {CC['bucket_results_reported']} bucket cells (all shown in regime_buckets.csv). With this many cuts, plenty of buckets look great in hindsight. In the heatmaps below, many cells are green on train and a largely different set is green in 2026. That is why only out-of-sample results count.

2a. The 32 out-of-sample regime tests

{tb(FMv,2)}

2b. Spot walk-forward: what was chosen each year and what happened next year

{tb(WF,2)}

The chosen label changes almost every year for P1, P2, P3, P7 and P8. A regime that has to be re-picked every year is not a stable property of the strategy.

2c. Recent-performance switch vs always-on (OOS mean net pts per trade)

{tb(MSp,2)}

M20 / M40 = last 20 / 40 trades net positive. 8w = last 8 weeks net positive. Only M20 is in the family; M40 and 8w are secondary. Turning a strategy off after losses mostly misses the rebounds: these strategies mean-revert in performance rather than trend.

2d. Lead: NR7 prior day for the breakout strategies (P1–P3 pooled; post-hoc check, not in the family)

{tb(N7s,2)}{tb(N7o,2)}

Spot net pts per trade by year. NR7 beat non-NR7 days in 2022, 2023, 2025 and 2026, and lost in 2024. The 2026 gap (+50 vs −4) is far larger than in any earlier year, so expect it to shrink. To watch going forward: log every NR7-day ORB/PDH breakout from 27-Oct-2026 and re-check after about 40 trades.

2e. Mean net pts by regime bucket (options; top = train, bottom = 2026)

Spot (top = 2022–25, bottom = 2026)

Full options bucket table{tb(RBo,1)}

3. By period

Month heatmaps (net pts per trade)

Options by year

{tb(pv(po,'year'),1)}

Options by half-year

{tb(pv(po,'half'),1)}

Options by quarter

{tb(pv(po,'quarter'),1)}

Spot by year

{tb(pv(ps,'year'),1)}

Spot by half-year

{tb(pv(ps,'half'),1)}

Spot by quarter

{tb(pv(ps,'quarter'),1)}

Monthly CSVs: options · spot.

Secondary exit (structural stop, hold to 15:14), options net pts

{tb(E1.pivot(index='cand',columns='split',values='mean').rename_axis(columns=None).reset_index(),2)}

Combinations: does agreement between strategies raise the probability?

Added after Satya's 21:13 IST question: “what about combination of strategies there probability increases right”. Pre-registered at 21:14 IST in PREREG.md before any combo result. Same data, costs, exit, random benchmark and train/test split. A combo trade is the trade of the confirming (later) signal, one per day. Honesty flag: the NR7 combos (N*) reuse a filter whose 2026 result was already seen, so they are not clean out-of-sample.

C1. Are the 8 independent? No.

Common window 21-Oct-2024 to 23-Oct-2026 ({OM['days']} days). Signal days: {', '.join(f"{k} {v}" for k,v in OM['fires'].items())}. Cell = % of days the row strategy fired on which the column strategy fired in the same direction.

{mx('same')}

ORB15 and ORB5 fire the same way on 87–89% of each other's days. When P3 (PDH/PDL) or P4 (CPR) fires, ORB15/ORB5 fire the same way on 75–82% of those days. The indicator group (P5 Supertrend, P6 EMA) agrees 70–84%. P8 (gap fade) is the ORB15 trade 90% of the time. When two of them “agree”, it is usually the same price move counted twice, not two independent confirmations. Option P&L on same-direction co-fire days is also highly correlated (P1–P2 0.91; P1–P8 1.00):

P&L correlation, opposite-direction rate, direction correlation, Jaccard

Correlation of option net P&L on same-direction co-fire days

{mx('pnlcorr',2)}

% of days with an opposite-direction signal

{mx('opp')}

Direction correlation on days both fire

{mx('dircorr',2)}

Jaccard same-direction overlap %

{mx('jaccard')}

C2. The 11 pre-declared combos (option net pts per trade, REAL costs, trail-50% exit)

{CTAB}

“Counts” needs q<0.10, a 2026 mean above its single strategies' 2026 mean, and ≥90th percentile vs random. PAIR_BEST turned out to be P3+P4, so it is the same test as PAIR_P3P4. BH over the 10 distinct tests gives the same answer (min q 0.16). Train pair ranking: all 28 pairs. Only P3+P4 was above 0 on train (+0.7, n=34). Every other pair lost.

Reading it. Requiring 2 or 3 strategies to agree lowered train expectancy compared with the single strategies (K3_30 −11.0 vs −5.9). By the time 3 signals agree, much of the move is already used up and the stop is wider. In 2026 the k-of-n combos were negative for Jan–Sep and positive only in October. P3+P4 (PDH/PDL breakout on a narrow-CPR day) is the only clean combo with a positive train and 2026 (+0.7 → +19.8, Jan–Sep 2026 +14.6, n=23). But it has 25 trades, q=0.13, a median train trade of −13, and depends on a few big days. It goes on the forward-watch list next to NR7.

C3. By quarter (options)

{tb(cq,1)}

C4. Spot-level check 2022–2026 (spot net pts, 1 pt cost)

{tb(csy,1)}

On spot, k-of-n agreement flips sign from year to year (K2_15: −0.2, −6.2, +7.3, −4.1, +4.0). P3+P4 needs VWAP for P4, so it only starts Oct-2024.

Premium support (“black box” / premium reversal): buy the option when its own premium bounces off support

Added after Satya's 21:24 IST question: “what about option premium support strategy”. Pre-registered at 21:26 IST in PREREG.md before any result. Real 1m ATM weekly option bars 21-Oct-2024 to 23-Oct-2026. The chart is the current ATM strike's own 5m premium chart. Costs ≈0.5 pt per side plus statutory charges (STT 0.15% of sell premium from 1-Apr-2026). Train Oct-2024–Dec-2025, test 2026. Random benchmark = same minute, same CE/PE, ATM on random days of the same split, same exit.

PS1. What the earlier (early-Oct 2026) work found

Study (nifty-setups-backtest)What it wasOut-of-sample result
atm-daily-srPrior-day ATM CE/PE premiums mapped to spot S/R rails (half-scale), bounce/retest entries; index book SL20, 40%@+20, 30%@+40, trail rest275 trades, −10.3 pts per trade net (−0.3 gross); old futures-style costs ~10 pts per trade
atm-daily-sr-optbuyerSame levels, booked as option buyer (SL10, 40%@+10, 30%@+20, trail)476 trades, −1.5 option pts per trade net, −0.5 gross, 47% win
confluence-v1Retailer trap + AMD fake-break + ATM premium black-box support bounce, all same direction20 trades, +7.2 gross but −4.2 net (cost ~11 pts per trade), p=0.48
confluence-v1-scaleoutSame entry, SL20 / 40%@+20 / 30%@+40 / trail exit30 trades, −0.4 gross, −12.2 net
trader-filters-r126Black box or round-50 + rejection wick (R1), plus R2/R6 filters, option buyerAll 8 parameter sets net negative (≈ −1.5 to −2 pts per trade, n≈1,000–2,000)
trader log (hand-logged stream calls)11 “Black Box” trades May–Jul 202610 of 11 marked WIN. These are discretionary, hand-logged calls with no fixed rule and no losers' log, so they cannot be reproduced or tested.

The confluence idea was the only one with a positive gross result, and that was on 20 trades. Under corrected costs (~2 pts per round trip instead of ~11), +7.2 gross would still be only about +5 net on n=20, which is not testable evidence.

PS2. The 4 pre-registered variants (option net pts per trade, corrected costs)

{PTAB}

V1 level = prior-day low of the same contract. V2 = most recent confirmed 5m swing low after 09:30. V3 = multiple of 25 (premium 40–400). V1–V3 need a touch within max(2 pts, 3%) approached from above, then the next 5m close above the touch-bar high. V4 = 5m close above the premium's high of the day so far. Entry is at the next 1m option open. First event per day. “Counts” needs q<0.10, ≥90th percentile vs random, and still positive without the best 3 trades. Pre-charge means include the 0.5 pt per side slippage.

Reading it. Every variant and both exits lost money in both periods, and no BH q is below 1.00. Several are worse than random entries at the same minute (V1, and V3 with the trail exit, sit at the 0–2nd percentile). A premium bouncing off its support does not predict the next move any better than chance. The prior-day premium low is the worst level, probably because overnight theta decay pulls the premium down to it mechanically rather than real buyers defending it. Satya's old scale-out exit and the trail-50% exit give the same answer. October 2026 does not rescue it either (Oct 2026: V3 trail +7.4 on 15 trades; the others are negative).

PS3. Regime filters chosen on train, applied unchanged to 2026 (BH over 8)

{tb(PFv,2)}

The train-chosen regime (VIX>16, NR7, uptrend, …) lost money in 2026 in all 6 cases where a filter qualified, and was worse than always-on in 5 of those 6 (V1 had no qualifying filter). 0 of 8 count.

PS4. By quarter (trail-50% exit)

{tb(pq('X_T50'),1)}

By quarter (old scale-out exit)

{tb(pq('X_OLD'),1)}

Monthly and regime-bucket tables: prem_by_period.csv · prem_regime_buckets.csv · trades prem_trades.csv.

Trader discretion: can the log's choices be turned into rules?

Added after Satya's 21:34 IST question: “why can't you find those discretion rules”. Design pre-registered at 21:37 IST in PREREG.md (TRADER-DISCRETION) before any taken/skipped or outcome number was seen. Real 1m Nifty ATM weekly option bars. Corrected costs (≈0.5 pt per side plus statutory charges, STT 0.15% of sell premium from 1-Apr-2026).

TD1. The data

The log is trader-log/trades_timestamped.csv: 225 rows, 06-May to 05-Oct-2026, built from stream screenshots and the fixed-date Excel files with entry premium and start/stop times. 218 rows are usable (one NO_TRADE row and rows with no time or strike removed): 110 Nifty and 108 Sensex. Sensex rows are mapped to the Nifty ATM equivalent at the same minute. Logged result: 169 WIN, 47 LOSS, 2 BE (78% wins).

How well the log matches real option bars (earlier fill-match work): only 22 of 218 (10%) match the real premium at the logged time (tiers A/B/B2/C: exact, within ±5 or ±15 minutes, or within 5% of the premium). 148 (68%) match only on strike and time, because the log has no usable premium. 47 (22%) disagree with the real bars (wrong premium, or the strike did not trade). When the earlier loose premium matcher was tried at shifted placebo times, it matched about as often as at the logged times (Nifty 29% vs 31% placebo). So the logged premiums do not reliably identify a real fill.

TD2. Entries by setup family, and what real bars say happened

{tb(dfam,0)}

Replay win = the earlier fill-match simulation with the logged stop and target on real 1m bars. X_OLD = Nifty ATM option bought at the next 1m open after the logged time, with the SL20 / 40%@+20 / 30%@+40 / trail exit. Covered = an entry of ours (V1–V4 premium-support or P1–P8) fired the same day, same direction, within ±10 minutes.

What this shows. On real bars the trader's calls are ordinary. The overall replay win rate is 27% versus 78% logged, and the Black Box calls go from 14 of 15 logged wins to 1 of 15 in the replay. Some of the gap is how the replay works: it uses the Nifty ATM and the next 1m open, while he may have used a different strike, a better fill or a manual exit. That gap can't be closed, because 68% of rows have no premium to check against. But a 78% → 27% drop is far too big to be explained by fills alone.

TD3. Which of our rule entries did he take?

Across the 53 days with log entries our rules made 662 candidate entries; 103 (15.6%) line up with one of his entries. Only 30% of his entries are matched by any rule of ours, and 19% by a premium-support rule. Placebo check: shift his entry times by ±30 or ±60 minutes and the match rate is 8.8–13.1%, or 11% if the direction is flipped. So his entries line up with our setups only slightly more often than chance. Most of what he does is not one of these setups.

{tb(dsrc,1)}

Outcome of candidates he took vs skipped (our exits, real bars)

{tb(dts,2)}

The taken candidates did no better than the skipped ones. They were better with the old exit (−2.1 vs −5.1) and worse with the trail-50% exit (−6.0 vs −3.0). Both groups lose money. Skipping those candidates did not avoid losers.

TD4. Taken vs skipped: entry-time features (effect size = standardised mean difference; Mann-Whitney p, BH over 17)

{tb(DFc,3)}

Only three features differ after BH. Time of day: taken entries come ~37 minutes earlier on average (SMD −0.41). Prior trades that day: more trades already that day (+0.29). ATM OI change over 15 minutes: OI rising faster (+0.30). Premium drawdown, speed of fall, bounce speed, distance from the day high/low and from round levels, gap, basis, PCR change and expiry day show no difference (|SMD| ≤ 0.14). “Prior trades” is a property of his day, not of the market, so it cannot become a rule.

TD5. Models with leave-one-day-out CV (fit May–Jul 2026, holdout Aug–Oct 2026)

ModelLeave-one-day-out AUC (May–Jul)Holdout AUC (Aug–Oct)
Depth-2 tree (min leaf 10){DM['tree']['lodo_auc']:.2f}{DM['tree']['holdout_auc']:.2f}
L2 logistic, 17 features{DM['logit']['lodo_auc']:.2f}{DM['logit']['holdout_auc']:.2f}

An AUC of 0.5 means the model does no better than a coin flip at telling taken from skipped. The logistic's 0.61 in CV comes almost entirely from time of day and prior trades (coefficients −1.2 and +0.8). It drops to 0.55 on the held-out months. This repeats the early-Oct remine (trader-filter-mine-timed), where the holdout AUC was ≈0.5 and the best broad rule (near the day extreme and a calm 3-minute move) had lift 1.24 with a CI of 0.84–1.61.

TD6. The pre-registered filter and its tests

Extraction rule (fixed in advance): take the tree leaf with the highest take rate among leaves with ≥10 takes, provided it is at least 1.2× the base rate. Result: {DRULE}. tod is minutes after 09:15, so 36.5 means before about 09:52 IST. On May–Jul this leaf took 34 of 65 candidates (52%) against a base rate of 16%.

Held-out log months (Aug–Oct 2026): the filter fired {DM['holdout_n']} times and he took {DM['holdout_takes']}. Lift {DM['holdout_lift']:.2f} (base {DM['holdout_base']*100:.0f}%), day-block bootstrap 90% CI {DM['lift_ci90'][0]:.2f}–{DM['lift_ci90'][1]:.2f}. Failed: the CI needed to be above 1.

As a strategy on 2024–26 history (May–Jul 2026 excluded; first filtered candidate per day; random = same candidate minute and type on random days, 30 repeats; BH over 4):

{tb(DH,2)}

0 of 4 count. Three of the four are negative and no better than random. The 2026 trail-50% row is +4.5 per trade at the 97th percentile vs random, but q=0.82 and −1.6 without its best 3 trades, the same October-heavy pattern as the rest of this page.

TD7. Why the discretion rules can't be found (bias and small-n disclosure)

Files: disc_trader_entries.csv · disc_candidates.csv · disc_feature_contrast.csv · disc_model.json · disc_filter_fullhist.csv. Scripts: s9_discretion.py, s9b_fullhist.py, s10_disc_model.py (sklearn), s11_disc_fullhist.py.

{YT_HTML} {EC_HTML} {W20_HTML} {NTV_HTML}

4. The 8 rules (as found in popular Indian trading guides)

{''.join(f'' for c in NAMES)}
#StrategyRule as coded
{c}{NAMES[c]}{html.escape(RULE[c])}

Sources: stoxgo.in CPR guide, algoram.in “best NIFTY options buying strategy 2026”, tradingzenith.net “7 setups tested”, Ojha CPR playbook. The VWAP-pullback and with-gap ORB variants were already tested on the buyer-method audit.

5. Method, data and limits

Files: results · scripts · charts · pre-registration.

""" open(f'{L}/index.html','w').write(H); print('written',len(H))