import pandas as pd,numpy as np,datetime as dt,html,json L='/workspace/research/buyer-method-audit'; 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='–') css=open('/workspace/research/flow-forensics-2026-10/index.html').read().split('')[0] CH=json.load(open(f'{L}/results/frozen_choices.json')) F=R('flaw_cost_exit_pooled.csv'); fp=F.pivot(index='ex',columns='scen',values='pnl') P=R('primary_results.csv'); PT=P[['cand','ex','split','n','mean','median','win','avg_win','avg_loss','payoff','rs_per_trade','rs_total','maxdd_rs','worst_month','worst_month_rs','random_mean','random_pctile','t_p_one','q_bh']] SP=R('spread_by_tod.csv'); SD=R('spread_by_dte.csv'); SO=R('spread_by_offset.csv') Y=R('chosen_by_year.csv'); G=R('grid_all_exits_scen.csv'); Gp=G[G.scen=='REAL'].pivot_table(index=['cand','ex'],columns='split',values=['n','mean']).round(2); Gp.columns=[f'{a}_{b}' for a,b in Gp.columns]; Gp=Gp.reset_index() TOD=R('random_by_tod.csv'); TODp=TOD.pivot(index='hm',columns='ex',values='pnl').reset_index() WV=R('random_with_vs_counter.csv'); SPX=R('spot_proxy_by_year.csv'); SPX=SPX[SPX.ex.isin(['E1','E4'])] DI=R('deep_itm_orb_E1_summary.csv'); DI=DI[DI.scen=='REAL'] names={'A':'O30 >150 continuation','B15':'ORB15 with-gap','B30':'ORB30 with-gap','C':'Rule B early-low rebound (CE)','D':'First-hour VWAP pullback','E':'Trend-day filter'} PT.insert(1,'name',PT.cand.map(names)) te=P[P.split=='test'].set_index('cand') H=f"""
Built {now}. Research only, not advice. Read-only data, no orders. Asked 26-Oct-2026 17:11 IST: “you are not able to find 1 strategy in option buying which is profitable, you are doing something wrong, there are thousands of strategies and you say all of them lose.”
Update 26-Oct-2026 (asked 18:06 IST): “trail the profit at 50% after 1:2” re-run → section 6. It does not change the verdict.
| # | Suspected flaw | Evidence in our code | Verdict | Measured impact |
|---|---|---|---|---|
| 1 | Slippage 3 pts/side | SLIP=3.0 in recov/tradelib.py, lows/part2_options.py, callwall/cwlib.py, pre-expiry/backtest_pre.py, last30/options_pnl.py, overnight/backtest.py. (nifty-setups-backtest used 0.3–0.5, which is fine.) | CONFIRMED, the main flaw | OLD→REAL: E0 {fp.loc['E0','OLD']:+.2f}→{fp.loc['E0','REAL']:+.2f}; E1 {fp.loc['E1','OLD']:+.2f}→{fp.loc['E1','REAL']:+.2f} pts/trade, i.e. +4.9 pts/trade (≈₹320/lot). Stress at 1 pt/side costs another −1.0. |
| 1b | STT | Old code used 0.10% on all sells. It has been 0.15% since 1-Apr-2026 (Finance Act 2026, NSE circular). | Confirmed, tiny | ≈−0.07 pt/trade in 2026 (it made old 2026 results slightly too good) |
| 2 | Premium stop −20 + scale-out cut the right tail | E0 in tradelib/cwlib: stop −20, 40% at +20, 30% at +40, 60-min cap | Not confirmed | Pooled REAL: E0 {fp.loc['E0','REAL']:+.2f} vs structural stop + hold to 15:14 {fp.loc['E1','REAL']:+.2f}, 4R {fp.loc['E3','REAL']:+.2f}, 2R {fp.loc['E2','REAL']:+.2f}, trail {fp.loc['E4','REAL']:+.2f}. Longer holds raise the payoff ratio (1.4–2.7) but win rates drop to 20–35% and theta is paid for longer. Only the trail is better, by ≈+0.4. |
| 3 | Entries were countertrend or late; first hour ignored | recov and lows were rebound fades. callwall was a mid-day cross. | Partly true, small | Random entries: with-trend {WV.with_trend.mean():+.2f} vs counter {WV.counter.mean():+.2f} (first hour {WV[WV.hm.isin(['09:30','09:45','10:15'])].with_trend.mean():+.2f} vs {WV[WV.hm.isin(['09:30','09:45','10:15'])].counter.mean():+.2f}). First-hour random entries {TOD[TOD.hm.isin(['09:20','09:30','09:45'])].pnl.mean():+.2f} vs later {TOD[~TOD.hm.isin(['09:20','09:30','09:45'])].pnl.mean():+.2f}, so no free first-hour edge. All 6 candidates here are with-trend and first-hour. |
| 4a | Lookahead / same-bar entry | All old option sims fill at the next minute's open. Stops fill at min(open, stop). | Not found | 0. One small optimism: recov 'spotstop' exits at the same bar's close. |
| 4b | Wrong strike | ATM only in recov/callwall | Minor | ITM1/ITM2/OTM1 vs ATM: −0.1 to +0.6 pt on average. 0DTE is worse (−0.5 to −1.1). |
| 4c | Spot close vs executable option price | Old trades already used real 1-min option bars | Not a flaw | – |
| 4d | Too many variants / BH kills everything | callwall picked 1 of 440 combos per side. Last30 had dozens of variants. | Real issue (it cuts both ways) | Here the family is fixed at 6 tests, declared before 2026 was opened. Nothing is positive enough for BH to even matter: the best raw p is {te.t_p_one.min():.2f}. |
| 4e | 60-min hold cap | recov/callwall used 60 min (runner 240) | Tested via E1–E4 (hold to 15:14) | Holding longer did not help on average (see #2) |
| 4f | Sessions excluded / missing data | Spot 15:15–15:29 frozen since 2026-08-03 is treated as missing. Options are real to 15:40. | Handled | The time exit is at the 15:15 option open, so the frozen window is never used. |
Bid-ask from the Upstox ATM-feed snapshots (strikes ATM±2, CE+PE, 08–26 Oct 2026). Spread in option points:
{tb(SP,3)}{tb(SD,3)}{tb(SO,3)}Charges (2-order round trip, ATM premium ≈145, lot 65): brokerage ₹40, STT 0.15% of sell premium ≈₹14, NSE txn 0.03503% ≈₹7, GST 18% on brokerage+txn ≈₹8.5, SEBI and stamp <₹1. Total ≈₹68 = 1.04 pts. Scale-out exits use 3–4 orders: 1.43–1.96 pts. Slippage: 0.5 pt/side used as REAL, against a measured half-spread of ≈0.13. That leaves room for a tick and for faster minutes. Stress case 1.0 pt/side. Total REAL ≈ 2.0 pts/trade (₹133/lot) versus the old ≈7.0–8.0. Caveat: the spreads come from calm-to-normal Oct-2026 sessions. Gap opens and news minutes can be wider.
Instrument: ATM, nearest weekly with DTE≥1, CE for long / PE for short. Entry at the next minute's option open after a spot-bar-close signal. Exits are decided on spot and executed at the next option open. Time exit at the 15:15 option open. R = structural stop distance clipped to 15–70 spot pts. Exit set E0 legacy / E1 structural stop + time / E2 2R / E3 4R / E4 BE at 1R then trail 1R. The exit is chosen per candidate on TRAIN (Oct-2024..Dec-2025), then tested once on 2026 (to 23-Oct). REAL cost. Random = same-time, same-direction, same-R entries on random days of the same period with the same exit; percentile = rank of the candidate mean among 2,000 random same-size sets. ₹ per lot uses lot 75 in 2024–25 and 65 in 2026.
{tb(PT)}
The trend-day filter (E) with the trail E4 is +18.4 pts in 2026, but −9.8 in train. It was the worst-ranked choice on train, so taking it now would be fitting to 2026. Not claimed.
Spot-only proxy (index points, no costs, same signal and exit; 2022–23 has no futures volume, so VWAP-based D/E start in Oct-2024):
{tb(SPX,1)}ORB with-gap held to 15:14 (E1): spot points vs what the option buyer keeps after REAL cost, by strikes ITM:
{tb(DI)}
By direction (all periods): on short signals spot gains +13.3 pts, the ATM put gains +3.2 before costs, and the 6-ITM put +6.6. On long signals spot loses −3.9 and calls lose −6. Over 3–5 hours a long weekly option gives back roughly 3–6 pts to theta and the falling intraday IV. That is the same size as the spot edge.
"""+open(f'{L}/section_trail50.html').read()+f"""Files: results · scripts · charts · pre-registration. Related: intraday recoveries, flow forensics, opening-30m rule.
""" open(f'{L}/index.html','w').write(H); print('written',len(H))