#!/usr/bin/env python3
"""ROT A2 Task 2 round 2 (pre-registered 7 Oct 2026, rules A2.R10..A2.R18 in registry.csv).
Same test as lead_test.py (signed lead, hit vs base, lift) PLUS the momentum control from r6_control.py on EVERY series.
Target: chain token vs BTC, log ratio, 1 day delay, 14 day forward. Eras 2019-22 and 2023-26.
PASS bar (fixed before running): median signed lead >= 2 days AND lift > 1 in >= 60% of tokens AND
'signal alone' (z>=2 while momentum z<1) beats all days by > 1% in BOTH eras AND regression coef > 0 in both eras."""
import pandas as pd, numpy as np, os, gzip
D = os.path.dirname(os.path.abspath(__file__)); os.makedirs(f"{D}/out", exist_ok=True)
a = pd.read_csv(f"{D}/data/flows_daily.csv.gz"); b = pd.read_csv(f"{D}/data/flows2_daily.csv.gz")
df = pd.concat([a, b]); df["date"] = pd.to_datetime(df["date"])
def wide(metric, src):
    x = df[(df.metric == metric) & (df.source == src)]
    return x.pivot_table(index="date", columns="entity", values="value", aggfunc="sum").sort_index().asfreq("D")
px = wide("close_usd", "binance")
rel = np.log(px.div(px["BTC"], axis=0)).drop(columns=["BTC"])
ART = {"ethereum": "ETH", "solana": "SOL", "bsc": "BNB", "arbitrum": "ARB", "tron": "TRX", "sui": "SUI", "avalanche": "AVAX",
       "sonic": "S", "ton": "TON", "polygon": "POL", "aptos": "APT", "optimism": "OP", "hyperliquid": "HYPE"}
WIK = {"Ethereum": "ETH", "Solana": "SOL", "BNB": "BNB", "Tron": "TRX", "TON": "TON", "Avalanche": "AVAX", "Polygon": "POL",
       "Dogecoin": "DOGE", "XRP": "XRP", "Hyperliquid": "HYPE", "Sui": "SUI"}
def ren(w, m): w = w.rename(columns=m); return w[[c for c in w.columns if c in rel.columns]]
z = lambda s, n=90: (s - s.rolling(n, min_periods=60).mean()) / s.rolling(n, min_periods=60).std()
lg = lambda w: np.log(w.where(w > 0))
def share(w): return w.div(w.sum(axis=1, min_count=4), axis=0)

bf, hf, of = wide("funding_daily", "bybit"), wide("funding_daily", "hyperliquid"), wide("funding_daily", "okx")
dau, nu, tx = (ren(wide(m, "artemis"), ART) for m in ["dau", "new_users", "txns"])
wiki = ren(wide("pageviews", "wikipedia"), WIK)
SERIES = {
 "A2.R10": ("Bybit funding 7d mean spike", ren(bf, {}).rolling(7, min_periods=5).mean()),
 "A2.R11": ("Hyperliquid funding 7d mean spike", ren(hf, {}).rolling(7, min_periods=5).mean()),
 "A2.R12": ("Hyperliquid funding 1d (faster) spike", ren(hf, {})),
 "A2.R13": ("Active wallets (DAU) 7d avg, 7d log change", lg(dau.rolling(7, min_periods=5).mean()).diff(7)),
 "A2.R14": ("New wallets 7d avg, 7d log change", lg(nu.rolling(7, min_periods=5).mean()).diff(7)),
 "A2.R15": ("Tx count 7d avg, 7d log change", lg(tx.rolling(7, min_periods=5).mean()).diff(7)),
 "A2.R16": ("Active wallet SHARE across chains, 7d change", share(dau.rolling(7, min_periods=5).mean()).diff(7)),
 "A2.R17": ("Wikipedia pageviews 7d avg, 7d log change", lg(wiki.rolling(7, min_periods=5).mean()).diff(7)),
}
H = 14
fwd = rel.shift(-H - 1) - rel.shift(-1)
def events(r):
    p = r - r.shift(14); on = (p > 0.10) & (p.shift(1) <= 0.10); ev, last = [], None
    for d in on[on].index:
        if last is None or (d - last).days > 14: ev.append(d)
        last = d
    return ev
def fires_of(zx):
    f = (zx >= 2) & (zx.shift(1) < 2); out, last = [], None
    for d in f[f].index:
        if last is None or (d - last).days > 14: out.append(d)
        last = d
    return out
rows, panel = [], []
for sid, (name, X) in SERIES.items():
    for ch in X.columns:
        x = X[ch]; r = rel[ch]; ok = x.notna() & r.notna()
        if ok.sum() < 200: continue
        zx = z(x); fires = fires_of(zx); ev = events(r)
        hit = lambda d: any(1 <= (e - d).days <= 30 for e in ev)
        valid = r.dropna().index; valid = valid[(valid >= x.dropna().index.min()) & (valid <= valid.max() - pd.Timedelta(days=31))]
        base = np.mean([hit(d) for d in valid[::3]]) if len(valid) else np.nan
        fh = [hit(d) for d in fires if d in valid]
        leads = [min(ds, key=abs) for e in ev for ds in [[(e - f).days for f in fires if abs((e - f).days) <= 30]] if ds]
        rows.append(dict(rule=sid, series=name, token=ch, n_days=int(ok.sum()), fires=len(fires),
            hit=np.mean(fh) if fh else np.nan, base=base, lift=(np.mean(fh) / base) if fh and base else np.nan,
            lead=np.median(leads) if leads else np.nan))
        mz = z(r - r.shift(14))
        panel.append(pd.DataFrame({"rule": sid, "token": ch, "y": fwd[ch], "sz": zx, "mz": mz}).dropna())
rep = pd.DataFrame(rows); P = pd.concat(panel); P["era"] = np.where(P.index < "2023-01-01", "2019-22", "2023-26")
ctl = []
for (sid, era), g in P.groupby(["rule", "era"]):
    if len(g) < 300: continue
    X = np.column_stack([np.ones(len(g)), g.sz, g.mz]); bb = np.linalg.lstsq(X, g.y, rcond=None)[0]
    res = g.y - X @ bb; se = np.sqrt(np.diag(np.linalg.inv(X.T @ X)) * res.var()) * np.sqrt(14)
    al = g[(g.sz >= 2) & (g.mz < 1)].y
    ctl.append(dict(rule=sid, era=era, n=len(g), coef=bb[1], t=bb[1] / se[1], all_days=g.y.mean(), alone=al.mean(), n_alone=len(al)))
ctl = pd.DataFrame(ctl); ctl["edge"] = ctl.alone - ctl.all_days
s = rep.groupby(["rule", "series"]).agg(tokens=("token", "count"), med_lead=("lead", "median"), med_lift=("lift", "median"),
    share_lift_gt1=("lift", lambda v: (v > 1).mean()), fires=("fires", "sum")).reset_index()
for era in ["2019-22", "2023-26"]:
    c = ctl[ctl.era == era].set_index("rule")
    s[f"t_{era}"] = s.rule.map(c.t); s[f"edge_{era}"] = s.rule.map(c.edge); s[f"n_alone_{era}"] = s.rule.map(c.n_alone)
def verdict(r):
    eras = [e for e in ["2019-22", "2023-26"] if pd.notna(r[f"edge_{e}"]) and r[f"n_alone_{e}"] >= 20]
    ok = (r.med_lead >= 2) and (r.share_lift_gt1 >= 0.6) and len(eras) == 2 and all(r[f"edge_{e}"] > 0.01 and r[f"t_{e}"] > 0 for e in eras)
    if ok: return "PASS"
    if len(eras) < 2 and (r.med_lead >= 2) and (r.share_lift_gt1 >= 0.6) and all(r[f"edge_{e}"] > 0.01 and r[f"t_{e}"] > 0 for e in eras): return "ONE ERA ONLY"
    return "KILLED"
s["verdict"] = s.apply(verdict, axis=1)
rep.round(4).to_csv(f"{D}/out/lead2_report.csv", index=False); ctl.round(4).to_csv(f"{D}/out/lead2_control.csv", index=False)
s.round(4).to_csv(f"{D}/out/lead2_summary.csv", index=False)
pd.set_option("display.width", 250); pd.set_option("display.max_columns", 30); print(s.round(3).to_string(index=False))

# A2.R18: venue fee share tripling inside a chain (launchpads only, no trading apps)
pad = a[a.metric == "pad_fees_usd"].copy(); pad["date"] = pd.to_datetime(pad.date)
pad[["slug", "chain"]] = pad.entity.str.split("|", expand=True)
APPS = {"axiom", "gmgn", "fomo-wallet", "photon", "bullx", "maestro", "bonkbot"}
pad = pad[~pad.slug.isin(APPS)]
NAT = {"Solana": "SOL", "BSC": "BNB", "Base": "ETH", "Robinhood Chain": None}
ev_rows = []
for chn, tok in NAT.items():
    w = pad[pad.chain == chn].pivot_table(index="date", columns="slug", values="value", aggfunc="sum").sort_index().asfreq("D").fillna(0)
    if w.empty: continue
    w7 = w.rolling(7, min_periods=7).mean(); tot7 = w7.sum(axis=1); sh = w7.div(tot7.where(tot7 > 2000), axis=0)
    tot = w.sum(axis=1); f30 = np.log(tot[::-1].rolling(30).sum()[::-1].shift(-1) / tot.rolling(30).sum())
    for v in sh.columns:
        on = (sh[v] >= 0.10) & (sh[v] >= 3 * sh[v].shift(7)); last = None
        for d in on[on].index:
            if last is not None and (d - last).days <= 30: last = d; continue
            last = d
            ev_rows.append(dict(chain=chn, venue=v, date=d.date(), share=round(sh[v][d], 3), share_7d_ago=round(sh[v].shift(7)[d], 3),
                pad_fees_next30_vs_prior30=round(f30.get(d, np.nan), 3), any_day_median=round(f30.median(), 3),
                token_vs_btc_14d=round(fwd[tok][d], 3) if tok and d in fwd.index else np.nan,
                any_day_tok=round(fwd[tok].mean(), 3) if tok else np.nan))
E = pd.DataFrame(ev_rows); E.to_csv(f"{D}/out/r18_venue_events.csv", index=False)
print("\nA2.R18 venue share tripling events"); print(E.to_string(index=False))
if len(E):
    g = E.groupby("chain").agg(n=("venue", "count"), med_fees=("pad_fees_next30_vs_prior30", "median"), any_fees=("any_day_median", "first"),
        hit_fees_up=("pad_fees_next30_vs_prior30", lambda v: (v > 0).mean()), tok=("token_vs_btc_14d", "mean"), any_tok=("any_day_tok", "first"))
    print(g.round(3).to_string())
