"""8:30 / 9:30 / 10:00 anchor interaction study, NQ 2021-2026.
Front month stitched by daily RTH volume winner. All moves in PERCENT.
"""
import pandas as pd, numpy as np, glob, json, datetime as dt

FILES = glob.glob(r"F:\SierraChart\Data\research\bars\NQ*_1m.parquet")
frames = []
for f in FILES:
    df = pd.read_parquet(f, columns=['ts_et','open','high','low','close','volume'])
    t = df['ts_et'].dt.time
    df = df[(t >= dt.time(8,25)) & (t <= dt.time(16,0))].copy()
    df['contract'] = f.split('\\')[-1].replace('_1m.parquet','')
    frames.append(df)
allbars = pd.concat(frames, ignore_index=True)
allbars['date'] = allbars['ts_et'].dt.date
allbars['time'] = allbars['ts_et'].dt.time

# front month = volume winner per date
vol = allbars.groupby(['date','contract'])['volume'].sum().reset_index()
winner = vol.sort_values('volume', ascending=False).drop_duplicates('date').set_index('date')['contract']
allbars = allbars[allbars['contract'] == allbars['date'].map(winner)]

rows = []
for d, g in allbars.groupby('date'):
    g = g.sort_values('ts_et')
    def bar(hh, mm):
        m = g[g['time'] == dt.time(hh, mm)]
        return m.iloc[0] if len(m) else None
    b830, b831 = bar(8,30), bar(8,31)
    b930, b1000 = bar(9,30), bar(10,0)
    if b830 is None or b930 is None or b1000 is None: continue
    rth = g[(g['time'] >= dt.time(9,30)) & (g['time'] <= dt.time(15,59))]
    if len(rth) < 300: continue  # skip half days
    post10 = g[(g['time'] >= dt.time(10,0)) & (g['time'] <= dt.time(15,59))]
    if len(post10) < 200: continue
    p830 = b830['open']            # last pre-data price
    p930 = b930['open']
    p1000 = b1000['open']
    close = rth['close'].iloc[-1]
    imp_rng = (max(b830['high'], (b831['high'] if b831 is not None else b830['high'])) -
               min(b830['low'],  (b831['low']  if b831 is not None else b830['low']))) / p830 * 100
    or30 = g[(g['time'] >= dt.time(9,30)) & (g['time'] <= dt.time(9,59))]
    rows.append(dict(
        date=str(d), p830=p830, p930=p930, p1000=p1000, close=close,
        imp_rng=imp_rng,
        ret_930_830=(p930/p830-1)*100,
        ret_1000_930=(p1000/p930-1)*100,
        ret_close_1000=(close/p1000-1)*100,
        ret_close_930=(close/p930-1)*100,
        mfe10=(post10['high'].max()/p1000-1)*100,
        mae10=(post10['low'].min()/p1000-1)*100,
        touch930_after10=float(post10['low'].min() <= p930 <= post10['high'].max()),
        touch830_after10=float(post10['low'].min() <= p830 <= post10['high'].max()),
        or_hi=(or30['high'].max()/p930-1)*100, or_lo=(or30['low'].min()/p930-1)*100,
    ))

df = pd.DataFrame(rows).sort_values('date').reset_index(drop=True)
print(f"sessions: {len(df)}  span {df['date'].iloc[0]} -> {df['date'].iloc[-1]}")

df['above930'] = df['p1000'] > df['p930']
df['above830'] = df['p1000'] > df['p830']
df['datadate'] = df['imp_rng'] >= df['imp_rng'].quantile(0.8)  # top-quintile 8:30 impulse

def cell(sub, label):
    n = len(sub)
    if n < 15: return None
    r = sub['ret_close_1000']
    return dict(label=label, n=n, win=100*(r>0).mean(), mean=r.mean(), med=r.median(),
                t=r.mean()/(r.std()/np.sqrt(n)),
                mfe=sub['mfe10'].median(), mae=sub['mae10'].median(),
                touch930=100*sub['touch930_after10'].mean(),
                touch830=100*sub['touch830_after10'].mean())

out = {'sessions': len(df), 'span': [df['date'].iloc[0], df['date'].iloc[-1]]}

print("\n=== 10:00 position vs anchors -> rest of day (10:00->close, %) ===")
cells = []
for a930 in (True, False):
    for a830 in (True, False):
        sub = df[(df['above930']==a930) & (df['above830']==a830)]
        c = cell(sub, f"{'>' if a930 else '<'}930 & {'>' if a830 else '<'}830")
        if c: cells.append(c); print(f"{c['label']:>12}: n={c['n']:4d} win {c['win']:.0f}% mean {c['mean']:+.3f}% med {c['med']:+.3f}% t={c['t']:+.1f} | medMFE {c['mfe']:+.2f}% medMAE {c['mae']:+.2f}% | touch930 {c['touch930']:.0f}% touch830 {c['touch830']:.0f}%")
out['cells_all'] = cells

print("\n=== same, DATA-IMPULSE days only (top-quintile 8:30 range) ===")
dcells = []
for a930 in (True, False):
    for a830 in (True, False):
        sub = df[df['datadate'] & (df['above930']==a930) & (df['above830']==a830)]
        c = cell(sub, f"{'>' if a930 else '<'}930 & {'>' if a830 else '<'}830")
        if c: dcells.append(c); print(f"{c['label']:>12}: n={c['n']:4d} win {c['win']:.0f}% mean {c['mean']:+.3f}% med {c['med']:+.3f}% t={c['t']:+.1f} | medMFE {c['mfe']:+.2f}% medMAE {c['mae']:+.2f}% | touch930 {c['touch930']:.0f}% touch830 {c['touch830']:.0f}%")
out['cells_data'] = dcells

print("\n=== quiet days (bottom 80% of 8:30 impulse) ===")
qcells = []
for a930 in (True, False):
    for a830 in (True, False):
        sub = df[~df['datadate'] & (df['above930']==a930) & (df['above830']==a830)]
        c = cell(sub, f"{'>' if a930 else '<'}930 & {'>' if a830 else '<'}830")
        if c: qcells.append(c); print(f"{c['label']:>12}: n={c['n']:4d} win {c['win']:.0f}% mean {c['mean']:+.3f}% med {c['med']:+.3f}% t={c['t']:+.1f} | medMFE {c['mfe']:+.2f}% medMAE {c['mae']:+.2f}% | touch930 {c['touch930']:.0f}% touch830 {c['touch830']:.0f}%")
out['cells_quiet'] = qcells

print("\n=== does the 8:30->9:30 move persist 9:30->close? ===")
pers = []
for lo, hi, lbl in [(-99,-0.30,'gap dn <-0.30%'), (-0.30,-0.10,'-0.30..-0.10'), (-0.10,0.10,'flat ±0.10'), (0.10,0.30,'+0.10..+0.30'), (0.30,99,'gap up >+0.30%')]:
    sub = df[(df['ret_930_830'] > lo) & (df['ret_930_830'] <= hi)]
    if len(sub) < 15: continue
    r = sub['ret_close_930']
    pers.append(dict(label=lbl, n=len(sub), win=100*(r>0).mean(), mean=r.mean(), med=r.median(),
                     t=r.mean()/(r.std()/np.sqrt(len(sub)))))
    print(f"{lbl:>16}: n={len(sub):4d} 9:30->close win {pers[-1]['win']:.0f}% mean {pers[-1]['mean']:+.3f}% med {pers[-1]['med']:+.3f}% t={pers[-1]['t']:+.1f}")
out['persistence'] = pers

print("\n=== full stack orderings at 10:00 ===")
stacks = []
def order_label(r):
    trio = sorted([('1000', r['p1000']), ('930', r['p930']), ('830', r['p830'])], key=lambda x: -x[1])
    return ' > '.join(t[0] for t in trio)
df['stack'] = df.apply(order_label, axis=1)
for lbl, sub in df.groupby('stack'):
    c = cell(sub, lbl)
    if c: stacks.append(c); print(f"{lbl:>20}: n={c['n']:4d} win {c['win']:.0f}% mean {c['mean']:+.3f}% med {c['med']:+.3f}% t={c['t']:+.1f} | touch930 {c['touch930']:.0f}%")
out['stacks'] = stacks

# baseline
r = df['ret_close_1000']
out['baseline'] = dict(n=len(df), win=100*(r>0).mean(), mean=r.mean(), med=r.median())
print(f"\nbaseline 10:00->close: win {out['baseline']['win']:.0f}% mean {r.mean():+.3f}% med {r.median():+.3f}%")

df.to_csv(r"C:\Users\curta\AppData\Local\Temp\claude\C--Users-curta\24bd6b63-4de5-4dfb-bfc7-6830ad723d0a\scratchpad\opens_daily.csv", index=False)
with open(r"C:\Users\curta\AppData\Local\Temp\claude\C--Users-curta\24bd6b63-4de5-4dfb-bfc7-6830ad723d0a\scratchpad\opens_results.json","w") as f:
    json.dump(out, f, indent=1, default=float)
print("\nsaved opens_daily.csv + opens_results.json")
