# WHY-20 (PREREG 20:20 IST 27-Oct-2026): level scan, round-number support, holds-at-L variant. Real bars; spot>=15:15 missing. import sys; sys.path.insert(0,'/workspace/research/popular-strategies/scripts') from ec_lib import * from multiprocessing import Pool LV=[5,10,15,20,25,30,40,50,75,100]; RL=[10,17,18,20,22,23,25,30,50]; HL=[10,15,20,25,30,40,50] WN=[('W1',0,105),('W2',105,225),('W3',225,315),('W4',315,345)] def wof(t): return next(w for w,a,b in WN if a<=t<=b) def otm(typ,K,sp): return (typ=='PE' and Ksp) def day(fp): ix,ds=os.path.basename(fp)[:-8].split('_'); dt=pd.Timestamp(ds); s=SP.get((ix,dt)) if s is None: return [],[],[],[] A,src=load(ix,dt); slip=0.5 if ix=='NIFTY' else 1.5 sc=DH['SENSEX'].open.get(dt,np.nan)/DH['NIFTY'].open.get(dt,np.nan) if ix=='SENSEX' else 1.0 sets=[('raw',1.0)]+([('scaled',sc)] if ix=='SENSEX' and not np.isnan(sc) else []) E1=[];E2=[];E3=[];E4=[] ss=pd.Series(s).ffill().values # moneyness check only (last known spot); no price filling used in any P&L for (typ,K),r in A.items(): c=r[3]; lo_=r[2]; v=r[4] for sn,f in sets: # 1. level scan: first close in [0.8L,1.2L] after an earlier close >1.2L for L0 in LV: L=L0*f; hi=1.2*L; lo=0.8*L; above=False for t in range(1,346): if not np.isnan(c[t-1]) and c[t-1]>hi: above=True if not above or np.isnan(c[t]): continue if lo<=c[t]<=hi and (np.isnan(v[t]) or v[t]>0) and otm(typ,K,ss[t]): o=outcome(ix,dt,A,typ,K,t,slip,0.05*L); o.update(ix=ix,date=dt,set=sn,L=L0,typ=typ,K=K,t=t); E1.append(o); break if c[t]level if sn=='raw' or ix=='SENSEX': for L0 in RL: L=L0*f; above=False for t in range(1,360): if not np.isnan(c[t-1]) and c[t-1]>L: above=True if not above or np.isnan(lo_[t]): continue if lo_[t]<=L and otm(typ,K,ss[min(t,359)]): w5=lo_[t+1:t+6]; w15=lo_[t+1:t+16]; c15=c[min(t+15,374)] if np.all(np.isnan(w15)): break E2.append(dict(ix=ix,date=dt,set=sn,L=L0,typ=typ,K=K,t=t,hold5=np.nanmin(w5)>=L-2*f if np.any(~np.isnan(w5)) else np.nan,hold15=np.nanmin(w15)>=L-2*f, hold15_pct=np.nanmin(w15)>=0.9*L,up15=(c15>L) if not np.isnan(c15) else np.nan)); break # 3. holds-at-L (raw levels only, plus scaled for Sensex) for sn,f in sets: for L0 in HL: L=L0*f for N in (10,20): best={} for (typ,K),r in A.items(): o_,h,l,c,v=r[0],r[1],r[2],r[3],r[4]; above=False; t0=None for t in range(1,346): if not np.isnan(c[t-1]) and c[t-1]>1.1*L: above=True if above and not np.isnan(c[t]) and 0.9*L<=c[t]<=1.1*L and otm(typ,K,ss[t]): t0=t; break if t0 is None or t0+N>=345: continue bl=l[t0+1:t0+N+1] if np.all(np.isnan(bl)) or np.nanmin(bl)<0.85*L: continue bh_=np.nanmax(h[t0+1:t0+N+1]); te=None for t in range(t0+N+1,346): if not np.isnan(l[t]) and l[t]<0.85*L: break if not np.isnan(c[t]) and c[t]>bh_: te=t; break if te is None: continue if typ not in best or te