# Expiry cheap counter-trend section (s15/s16/s17); builds EC_HTML import pandas as pd RSE=f'{L}/results' EF=pd.read_csv(f'{RSE}/ec_freq.csv'); EY=pd.read_csv(f'{RSE}/ec_freq_day_year.csv'); EP=pd.read_csv(f'{RSE}/ec_primary.csv'); ES=pd.read_csv(f'{RSE}/ec_sens.csv'); ER=pd.read_csv(f'{RSE}/ec_regime.csv'); EL=pd.read_csv(f'{RSE}/ec_fills.csv') def fr(ix,s): return EF[(EF.ix==ix)&(EF.set==s)].iloc[0] fn,fs=fr('NIFTY','counter 15–25'),fr('SENSEX','counter 15–25') EFt=EF[['ix','set','expiry_days','signals','hit2x','hit3x','hit5x','med_min_to_peak','near_zero_settle']].rename(columns={'set':'signal set','hit2x':'reached 2× %','hit3x':'reached 3× %','hit5x':'reached 5× %','med_min_to_peak':'median min to peak','near_zero_settle':'settled ≈0 %'}) EPt=EP[['ix','exit','split','n','mean','median','win','p10','p90','best','no_top3','with_trend_mean','random_mean','random_pctile','t_p','q_bh','counts']].rename(columns={'mean':'mean net pts','win':'win %','no_top3':'mean w/o top 3','with_trend_mean':'with-trend control','random_mean':'random-minute mean','random_pctile':'pctile vs random','t_p':'p','q_bh':'BH q (6)'}) st=ES[ES.split=='test']; npos=int((st['mean']>0).sum()) ERt=ER.rename(columns={'test_no_top3':'test w/o top 3'}) EC_HTML=f"""
Added after Satya's 19:55 IST (27-Oct) question: “on expiries opposite to trend from around 20 premium there is 2x or 3x jumps”. Design pre-registered at 20:05 IST in PREREG.md (EXPIRY-CHEAP-COUNTER) before any number was computed. Real 1m bars of the expiring contract:
Spot 15:15–15:29 is treated as missing. Settlement uses the official close. Option buyer only.
“Reached N×” = a later 1m high ≥ N × the signal-minute close, at any time up to 15:29. Measured from the fill (next open +slippage), ≥2× drops slightly. The median signal minute is 09:36, because a 15–25 option on the counter side usually already exists right after the trend check starts. Sensex points are about 3.3× Nifty points, so a raw 15–25 Sensex option is much further OTM; the “scaled band” row fixes that.
Share of expiry days where a counter-trend first signal (either side) reached 2× / 3× / 5×, by year:
{tb(EY,1)}Quarterly: ec_freq_quarter.csv.
Net option points per unit (Sensex rows are in Sensex points; divide by ≈3.3 for Nifty-equivalent). A row counts only if the test mean > 0, p < 0.05, q < 0.10, it beats random (≥90th percentile) and the with-trend control, the train mean > 0, and the mean without the top 3 is > 0.
Why “2×–3× happens 20–40% of the time” still loses money: the median trade loses most of the premium. Exit (a) wins only 1 trade in 4 to 6, and most of the rest expire near zero, so the 2× and 3× winners don't pay for them. Hold-to-settlement has rare huge winners (best +{EP[EP.exit=='hold to settlement'].best.max():.0f} pts) but a win rate of 3–14%, and it is negative without the top 3. The p < 0.05 rows are significantly negative.
The best train buckets were mostly still negative on train. The two that were positive on train both failed in 2026: Nifty DMA-trend up, hold to settlement (+8.2 → −7.8), and Sensex ER20 mid, trail (+0.0 → −9.1). The only positive test bucket (Sensex hold, +9.3, n=19) had a train mean of −4.6, wins on 1 of 19 trades, and is −23.5 without its top 3.
Every signal and fill bar had traded volume (median about 3.0M Nifty and 0.37M Sensex units at the fill minute), so the prints are real trades, not stale quotes. Bid/ask spreads are unknown (no history). On a 20-premium option a 0.5–1 pt spread is 2.5–5% per side, so real results would be somewhat worse than shown.
Files: ec_signals.csv (every signal with outcome) · ec_random.csv · ec_freq.csv · ec_primary.csv · ec_sens.csv · ec_regime.csv · ec_fills.csv · scripts s15_ec_cache.py, s16_ec.py, s17_ec_stats.py.
"""