import pandas as pd,numpy as np,datetime as dt,html R='/workspace/research/flow-forensics-2026-10/recov' now=dt.datetime.now(dt.timezone(dt.timedelta(hours=5,minutes=30))).strftime('%Y-%m-%d %H:%M IST') def tb(df,fl=1): return df.to_html(index=False,float_format=lambda x:f'{x:,.{fl}f}',border=0,na_rep='–') Y=pd.read_csv(f'{R}/results/freq_by_year.csv') M=pd.read_csv(f'{R}/results/freq_by_month.csv').tail(15) U=pd.read_csv(f'{R}/results/feature_univariate.csv') Up=U.pivot(index='feature',columns='split',values=['hit_true','hit_false','n_true','p','q_bh']) Up.columns=[f'{a}_{b}' for a,b in Up.columns]; Up=Up.reset_index()[['feature','n_true_train','hit_true_train','hit_false_train','p_train','n_true_test','hit_true_test','hit_false_test','p_test','q_bh_test']].sort_values('p_train') RR=pd.read_csv(f'{R}/results/rules_oos.csv') Q=pd.read_csv(f'{R}/results/squeeze_summary.csv') TS=pd.read_csv(f'{R}/results/trade_summary.csv'); TS=TS[(TS.expiry_rule=='nearest')&TS.group.isin(['all_candidates','rule_B_top3','rule_A_hypothesis'])][['group','split','style','n','mean_pts','median_pts','win_pct','total_rs_1lot','avg_cost_pts','random_mean','pctile_vs_random','p_mean_gt0']] ST=pd.read_csv(f'{R}/results/squeeze_trades.csv',parse_dates=['date']) STs=ST.groupby(['kind','test','style']).agg(n=('pnl','size'),mean_pts=('pnl','mean'),median_pts=('pnl','median'),win_pct=('pnl',lambda x:(x>0).mean()*100),total_rs_1lot=('pnl_rs_lot','sum')).reset_index() STs['test']=STs.test.map({True:'test 2026',False:'train 2024-25'}) DL=pd.read_csv(f'{R}/results/day_low_compare.csv'); DT=pd.read_csv(f'{R}/results/day_low_compare_timematched.csv') T=pd.read_csv(f'{R}/results/trades_all.csv',parse_dates=['date']); C=pd.read_csv(f'{R}/results/candidates_scored.csv',parse_dates=['date']) b=T[(T.kind=='candidate')&(T.expiry_rule=='nearest')].merge(C[['rule_B_train_top3_all']],left_on='idx',right_index=True); b=b[b.rule_B_train_top3_all] b['ym']=b.date.dt.strftime('%Y-%m'); BM=b.pivot_table(index='ym',columns='style',values='pnl',aggfunc='sum').round(1).reset_index(); BM.insert(1,'trades',b[b['style']=='runner'].groupby('ym').size().reindex(BM.ym).values) BY=pd.read_csv(f'{R}/results/ruleB_trades_by_year.csv') oct_=pd.read_csv(f'{R}/results/day_events.csv',parse_dates=['date']); oct_=oct_[oct_.date>='2026-10-01'][['date','gap','open','low','t_low','fall_pc','rec','t_rechigh','rng','ev200']]; oct_['date']=oct_.date.dt.strftime('%a %d-%b') tc=C[C.date>='2026-10-26'][['hm','RL','entry','fall','min_since_open','bounce_min','vix_lvl','put_oi_chg_pct','call_oi_chg_pct','basis_chg','bn_lead_min','heavy_put_dist','rule_B_train_top3_all','rule_A_hypothesis','maxrun','y200']] css=open('/workspace/research/flow-forensics-2026-10/index.html').read().split('')[0] H=f"""
Built {now}. Research only, not advice; read-only data, no orders. Asked 26-Oct-2026 12:41 IST: “today also 200 points move from bottom … there is something going on you are not able to analyze or catch”. Companion to flow forensics 12–23 Oct (which judged lows by the NEXT day; this page judges the SAME day).
Update 26-Oct-2026 (asked 16:20 IST): “call-wall cross squeeze” trigger tested on 2 years of 1-min option OI → section 8. Verdict: no out-of-sample edge for a buyer.
Event = Nifty spot rallies ≥X pts from its intraday low (09:15–15:14; spot 15:15–15:29 frozen since 03-Aug-2026, so excluded for all years). Source: 1-min Nifty 2022-01-03 → 2026-10-23 + today (Upstox intraday, to 12:42 IST). ev_0p9pct = recovery ≥0.9% (≈200 pts at today’s level). v_share_of_big = of days with ≥200 range, % where the ≥200 move was low-first.
Sharp-fall days (≥150 from prior close or from the high before the low) since 21-Oct-2024 with option-OI data. Naive comparison (recovered ≥200 vs recovered <100) — almost everything “differs”, but it is mostly because the non-recovery lows are made at the end of the day (median 14:51), when OI, VIX and time-left all behave differently:
{tb(DL,3)}Time-matched (only lows made by 12:00): 59 recovered vs 30 did not — 66% of sharp-fall days whose low was in by noon recovered ≥200, but nothing measured at the low separated the two groups (all BH q ≥ 0.39). Weakest-but-present: higher VIX level (16.1 vs 14.7, p=0.019), basis rising into the low slightly less on recovery days (p=0.05).
{tb(DT,3)}Candidate = after a ≥120-pt fall (from the higher of prior close or the session high before the low), the first 1-min close ≥30 pts above the running low within 15 min of that low (one per low; a new low ≥5 pts lower re-arms; max 3/day; triggers up to 14:14). Success = high reaches low+200 before price breaks low−5, by 15:14. 551 triggers on 291 days, 21-Oct-2024 → 26-Oct-2026. Train 2024-10-21…2025-12-31 (n=303, base 13.5%); test 2026-01-01…2026-10-26 (n=248, base 14.5%). Flow features: put OI over the 4 strikes around the low and call OI over 4 strikes 100–250 above (nearest weekly expiry, 1-min OI), from 10 min before the low to the trigger; basis = front-month future − spot; Bank Nifty and VIX 1-min from Upstox.
All breakouts to a new post-low closing high, 40–150 pts above the low, ≥5 min after it. Price-only = first such breakout per low; squeeze = first one with near-money call OI −2% or more in 5 min AND basis +3 or more in 5 min (hypothesis taken from the 23-Oct who-flow finding; 23/26-Oct excluded in one row to avoid self-confirmation).
{tb(Q,2)}ATM CE (strike = trigger close rounded to 50), nearest weekly expiry (skip-0DTE version in CSV — same picture). Fill = next minute’s open + 3 pts; exits − 3 pts; charges: ₹20/order brokerage, STT 0.1% on sells, exchange 0.03503%, SEBI, stamp 0.003% on buys, 18% GST; lot 75 (65 from Jan-2026). Styles: sl_only −20 premium stop, 60-min time exit; scale 40% at +20, 30% at +40, trail; spotstop exit if spot breaks the low (−40 premium backstop) + scale; runner exit when spot reaches low+200 or breaks the low (−35 backstop), else 15:14 (added after first results). Random benchmark = 2,974 random minutes 09:20–14:14 on the same days.
{tb(TS,2)}Prior close 22,600; open 22,524.4 (gap −75.6); low 22,387.35 at 09:26 (09:25 bar 22,387.45) — just above Friday’s low 22,372; Bank Nifty low 53,777.5 at 09:25 (1 min earlier); VIX peaked 18.00 at 09:43; high 22,706 at 10:08 (+319 from low); 12:42 at 22,600.
Timing rule B fired at 09:16 (failed: low later broke) and 09:26 (worked). Hypothesis rule A did not fire (basis was falling, heavy put strike 22500 was 113 pts above the low). Squeeze trigger did not fire within its +150 cap. Trades: 09:16 → 22500 CE fill 173.3, stopped −24 pts (−₹1,561/lot); 09:26 → 22400 CE fill 173.6: +23 (scale), +131 (runner), +188 (60-min hold).
results/ (day_events, freq_by_year/month, candidates, feature_univariate, rules_oos, trades_all, trade_summary, squeeze_*, day_low_*, today_minutes) · scripts/ · charts in charts/.
{open('/workspace/research/flow-forensics-2026-10/callwall/section_callwall.html').read()} """ open(f'{R}/index.html','w').write(H); print('page',len(H))