# shared from s16_ec.py (definitions only) # EXPIRY-CHEAP-COUNTER (PREREG 20:05 IST 27-Oct-2026). Signals, frequency facts, buyer exits a/b/c, controls. Real bars only; spot >=15:15 missing. import pandas as pd,numpy as np,glob,os L='/workspace/research/popular-strategies'; C=f'{L}/data/ec_cache'; CD='/workspace/research/common/data' rng=np.random.default_rng(20261027) DH={ix:pd.read_parquet(f'{CD}/{ix.lower()}_daily_hist.parquet').assign(date=lambda x:pd.to_datetime(x.date)).set_index('date') for ix in ('NIFTY','SENSEX')} DH['NIFTY'].loc[pd.Timestamp('2026-10-27'),['open','close']]=[22708.80,22749.75] # published official close (ET Now / Business Standard / Yahoo) SP={} for ix in ('NIFTY','SENSEX'): d=pd.read_parquet(f'{CD}/{ix.lower()}_1m_clean.parquet'); d['mi']=d.timestamp.dt.hour*60+d.timestamp.dt.minute-555 for dt,g in d.groupby('date'): a=np.full(375,np.nan); ok=(g.mi>=0)&(g.mi<375)&(~g.missing); a[g.mi[ok].values]=g.close[ok].values; SP[(ix,dt)]=a e=pd.read_parquet('/workspace/market-live-stack/bots/upstox/eod/2026-10-27/nifty_1m_2026-10-27.parquet'); ts=pd.to_datetime(e.timestamp); a=np.full(375,np.nan); a[(ts.dt.hour*60+ts.dt.minute-555).values]=e.close.values; SP[('NIFTY',pd.Timestamp('2026-10-27'))]=a for k in SP: SP[k][360:]=np.nan # spot 15:15-15:29 treated as missing (uniform rule) LAB=pd.read_csv(f'{L}/results/regime_labels.csv',parse_dates=['date']).set_index('date') W={'W1':(20,105),'W2':(105,225),'W3':(225,315),'W4':(315,345),'ALL':(20,345)} def lot(ix,t): if ix=='NIFTY': return 25 if t0); brk=20.0*n; sv=sum(f*p for f,p in sells)*Q; bv=buy*Q stt=(0.0015 if t>=pd.Timestamp('2026-04-01') else 0.001)*sv; tx=(0.0003503 if ix=='NIFTY' else 0.000325)*(bv+sv); sebi=1e-6*(bv+sv); st=3e-5*bv; gst=0.18*(brk+tx+sebi) return (brk+stt+tx+sebi+st+gst)/Q def load(ix,dt): d=pd.read_parquet(f'{C}/{ix}_{dt.date()}.parquet'); A={} for (t,K),g in d.groupby(['typ','K']): r=np.full((6,375),np.nan); m=g.mi.values; r[0,m]=g.o; r[1,m]=g.h; r[2,m]=g.l; r[3,m]=g.c; r[4,m]=g.v; r[5,m]=g.oi; A[(t,K)]=r return A,d.src.iloc[0] def trend(s): tr=np.zeros(375,int); o=s[0] if not np.isnan(s[0]) else np.nan for t in range(20,346): if np.isnan(s[t]) or np.isnan(s[t-20]): continue up=s[t]>OPEN and s[t]-s[t-20]>0; dn=s[t]=spot) or (typ=='CE' and K<=spot): continue c=r[3,t] if np.isnan(c) or chi: continue if not np.isnan(r[4,t]) and r[4,t]<=0: continue if decay and not (np.nanmax(r[3,:t]) if t>0 and np.any(~np.isnan(r[3,:t])) else 0)>=2*hi: continue key=(abs(c-(lo+hi)/2),abs(K-spot)) if best is None or key=2*fill: s1=2*fill if r[1,y]>=3*fill: s2=3*fill; s1=s1 or 2*fill; break if s1 is None: s1=r[3,last]-slip if s2 is None: s2=r[3,last]-slip s1=max(s1,0.05); s2=max(s2,0.05); out['pnl_a']=0.5*(s1-fill)+0.5*(s2-fill)-chg(ix,dt,fill,[(0.5,s1),(0.5,s2)]); out['gross_a']=0.5*(s1+s2)-fill # (b) stop 50% (low, first), trail 50% of peak gain after gain>=2R (R=0.5 fill), on closes R=0.5*fill; peak=0; ex=None; why='close' for y in range(f,last+1): if np.isnan(r[2,y]): continue if r[2,y]<=fill-R and y>f or (y==f and r[2,y]<=fill-R): ex=fill-R-slip; why='stop'; break g=r[3,y]-fill; peak=max(peak,r[1,y]-fill if r[1,y]-fill>=2*R else g) if peak>=2*R and g<=0.5*peak: ex=r[3,y]-slip; why='trail'; break if ex is None: ex=r[3,last]-slip ex=max(ex,0.05); out['pnl_b']=ex-fill-chg(ix,dt,fill,[(1,ex)]); out['why_b']=why # (c) settlement intrinsic, no slippage out['pnl_c']=(settle-fill-chg(ix,dt,fill,[(1,settle)])) if not np.isnan(settle) else np.nan return out