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"""Nifty same-day 200-pt recoveries from the low: what is going on, can a buyer catch it?

Nifty “falls hard, then rallies 200–300 from the low” — what is going on, and can an option buyer catch it live?

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.

Plain answer.
  1. Yes, it is happening more — because the daily range has blown out, not because someone is reliably buying every low. In Oct-2026, 8 of 16 sessions recovered ≥200 pts from the intraday low (2022–25 average 19.8% of days; binomial p=0.007). But 13 of 16 October days had a ≥200-pt range (normal 38.6%, p=0.001). Of those big-range days, the share where the low came first and price recovered (a “V”) was 8/13 = 62% vs 51% normally (p=0.58, not unusual). The other October big days (8, 12, 19, 21-Oct) fell 200–420 and did not recover — they are easy to forget. Aug–Sep 2026 had almost none (1 of 42 days).
  2. The “someone is defending put strikes / call writers squeezed” story is real on individual days but does not predict the bounce. At the moment a live trader would act (first 30-pt bounce after a ≥120-pt fall), put-OI building, call-OI dropping, futures-basis widening, Bank Nifty bottoming first, VIX falling, straddle crush, a heavy put strike under the low — none separated winners from losers out-of-sample. The hypothesis rule (heavy put strike + put writing + basis widening + Bank Nifty first) fired 11 times in 2026 and hit 0 (base 14.5%).
  3. What did separate: timing and volatility, not flow. A 2024–25-trained rule — low made in the first 30 min, bounce of 30 pts within 5 min of the low, India VIX ≥ 14.46 — hit +200 from the low in 17/59 = 28.8% of 2026 triggers vs base 14.5% (binomial p=0.003, BH q=0.024, 99.9th percentile of random same-size picks). So it doubles the odds — but 7 of 10 triggers still fail.
  4. After costs, buying a call on it still does not make reliable money. ATM call, next-minute fill +3 slippage, real 1-min option bars, full Zerodha-style charges: OOS 2026 −4.4 to −6.7 pts/trade with stop/scale-out styles; only a “hold until spot +200 from low or spot breaks the low” runner (added after the first results — treat as exploratory) was +2.0 pts/trade (59 trades, +₹7,567/lot in 10 months, p=0.39 — indistinguishable from zero; it lost −10.4 pts/trade in 2024–25).
  5. A “call-writer squeeze” trigger (breakout above the post-low base while near-money call OI drops ≥2% in 5 min and basis widens ≥3) is the best flow signal found: 37% reach +200 from the low vs 18.6% for the same price breakout without the flow condition (2026 test, p=0.004). But it fires late (median +91 pts off the low), so the gain left is small: OOS trades −0.3 pts/trade (spot stop, 59 trades) — break-even, not an edge. Today it did not fire in time (the call covering started at 09:52 when price was already +143 off the low and accelerated after +150).
  6. Today 26-Oct: the timing rule fired twice: 09:16 at 22,510 (failed, −24 pts on the 22,500 CE) and 09:26 at 22,418, one minute after the 22,387 low (Bank Nifty bottomed 09:25), next-minute fill on 22,400 CE ≈173.6 → +23 (scale-out), +131 (runner, out at 09:56 when spot hit +200) or +188 (60-min hold). Net of both triggers: positive today — but the same rule loses on most days in the record.
Bottom line: the thing you are seeing is a high-volatility regime (VIX 16–18) where intraday swings of 200–400 happen almost daily in either direction. In that regime, an early-session low with a sharp first bounce is the best (still only ~30%) tell. Order-flow footprints are visible after the move starts, not before.

1. How often does a ≥200-pt same-day recovery happen?

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.

{tb(Y)}

Last 15 months

{tb(M)}

Every October-2026 session

{tb(oct_)}

2. What looked different at the actual day low (hindsight)

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)}

3. Live detector (only data known at the trigger minute)

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.

Single features (Fisher exact; BH across 21 features)

{tb(Up,3)}

Rules — hit rate vs base, binomial p, percentile vs 5,000 random same-size picks

{tb(RR,3)}

“Call-writer squeeze” trigger vs the same price breakout

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)}

4. Option-buyer trades (after costs)

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)}

Timing rule (B) per year and per month (pts per trade, sum)

{tb(BY,1)}{tb(BM,1)}

Squeeze trigger trades

{tb(STs,2)}

5. Today, 26-Oct-2026 (case study)

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.

Detector rows today

{tb(tc,2)}

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).

6. Data gaps and caveats

7. Files

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))