"""_audit_tokyo_fix_20261010.py - independent audit of 2.I "Tokyo Fix Gotobi" (claimed t = +7.52).

Tested by: Claude (2026-10-10). Diagnostic, 0-ledger (audit of an external claim, no new rule).
Checks 9-15 of CLAUDE.md section 5b: clock, leak test (lag 0/1/2), increment kept-dropped,
independent vendor (HistData EST mid), power.

Rule under audit (TOKYO_FIX_GOTOBI_USDJPY_RESEARCH.md section 2):
  condition  price(09:55 JST) - price(08:30 JST) > 0
  short at bid 09:55 JST (00:55 UTC), SL 20 pips, exit at ask 15:00 JST (06:00 UTC), +0.5 pip friction.

Hypothesis of the bug: the original scripts group bars by UTC calendar date. Inside one UTC date
23:30 UTC comes 22.5 hours AFTER 00:55 UTC, so "pre-fix move" = P(00:55) - P(23:30 same UTC date)
is the sign of the FUTURE move. Variant A reproduces that; variant B uses 23:30 UTC of the day before.
"""
import math
import sys

import numpy as np
import pandas as pd

OANDA = r"<repo>\quantlab\data\inbox\oanda_m1\usdjpy_m1.parquet"
HIST = r"<repo>\quantlab\data\inbox\new_markets\usdjpy_m1_est.parquet"
SL_PIPS, EXTRA_PIPS, PIP = 20.0, 0.5, 0.01


def tstat(x):
    x = np.asarray(x, float)
    x = x[~np.isnan(x)]
    n = len(x)
    if n < 3 or x.std(ddof=1) == 0:
        return float("nan")
    return float(x.mean() / x.std(ddof=1) * math.sqrt(n))


def line(lbl, r):
    r = pd.Series(r).dropna()
    print(f"  {lbl:<46} n={len(r):5d}  mean={r.mean():+.4f}R  win={(r > 0).mean() * 100:5.1f}%  "
          f"sum={r.sum():+8.1f}R  t={tstat(r):+6.2f}")


def welch(a, b):
    a, b = pd.Series(a).dropna(), pd.Series(b).dropna()
    se = math.sqrt(a.var(ddof=1) / len(a) + b.var(ddof=1) / len(b))
    return a.mean() - b.mean(), (a.mean() - b.mean()) / se


def at(df, col, hhmm):
    """value of `col` at the bar stamped hh:mm UTC, one row per UTC date (unique by construction)."""
    h, m = hhmm
    s = df.loc[(df.index.hour == h) & (df.index.minute == m), col]
    s.index = s.index.normalize()
    assert s.index.is_unique
    return s


def build(df, bid_o, ask_o, ask_h, cost_extra_pips):
    """One row per UTC date D (= Tokyo trading morning). Entry 00:55 UTC, exit 06:00 UTC."""
    entry = at(df, bid_o, (0, 55))
    exit_ = at(df, ask_o, (6, 0))
    p2330 = at(df, bid_o, (23, 30))            # stamped on its own UTC date
    days = entry.index.intersection(exit_.index)
    days = days[days.dayofweek < 5]
    out = pd.DataFrame(index=days)
    out["entry"] = entry.reindex(days)
    out["exit"] = exit_.reindex(days)
    # A (as in the original scripts): 23:30 of the SAME UTC date = 22.5 h after entry
    out["pre_same_utc_date"] = (out["entry"] - p2330.reindex(days)) / PIP
    # B (causal): 23:30 UTC of the previous calendar day = 08:30 JST of the same Tokyo morning
    prev = p2330.copy()
    prev.index = prev.index + pd.Timedelta(days=1)
    out["pre_causal"] = (out["entry"] - prev.reindex(days)) / PIP
    # stop: ask high inside [00:55, 06:00) reaches entry + 20 pips
    win = df.loc[((df.index.hour * 60 + df.index.minute) >= 55) & ((df.index.hour * 60 + df.index.minute) < 360), ask_h]
    mx = win.groupby(win.index.normalize()).max().reindex(days)
    stopped = mx >= out["entry"] + SL_PIPS * PIP
    pips = (out["entry"] - out["exit"]) / PIP
    pips = pips.where(~stopped, -SL_PIPS) - cost_extra_pips
    out["R"] = pips / SL_PIPS
    out["pips_nostop"] = (out["entry"] - out["exit"]) / PIP - cost_extra_pips
    return out.dropna(subset=["entry", "exit"])


def report(name, t):
    print(f"\n=== {name}: {t.index.min().date()} -> {t.index.max().date()}, {len(t)} Tokyo mornings ===")
    line("unconditional short (every day)", t["R"])
    a_keep, a_drop = t.loc[t["pre_same_utc_date"] > 0, "R"], t.loc[t["pre_same_utc_date"] <= 0, "R"]
    line("A original filter (23:30 SAME utc date) > 0", a_keep)
    line("A dropped", a_drop)
    d, w = welch(a_keep, a_drop)
    print(f"    A kept - dropped = {d:+.4f}R, Welch t = {w:+.2f}")
    b_keep, b_drop = t.loc[t["pre_causal"] > 0, "R"], t.loc[t["pre_causal"] <= 0, "R"]
    line("B causal filter (23:30 PREVIOUS day) > 0", b_keep)
    line("B dropped", b_drop)
    d, w = welch(b_keep, b_drop)
    print(f"    B kept - dropped = {d:+.4f}R, Welch t = {w:+.2f}")
    for lag in (1, 2):
        k = t["pre_causal"].shift(lag) > 0
        d, w = welch(t.loc[k, "R"], t.loc[~k, "R"])
        print(f"    B filter lagged {lag} day(s): kept - dropped = {d:+.4f}R, Welch t = {w:+.2f}")
    print("  B causal, by half / year:")
    bk = t.loc[t["pre_causal"] > 0]
    line("  2015-2020", bk.loc[bk.index.year <= 2020, "R"])
    line("  2021-2026", bk.loc[bk.index.year >= 2021, "R"])
    yy = bk.groupby(bk.index.year)["R"].agg(["size", "mean", "sum"])
    print("   ", "  ".join(f"{y}:{r['sum']:+.1f}R" for y, r in yy.iterrows()))
    line("B causal, no stop (pips/20)", bk["pips_nostop"] / SL_PIPS)
    return bk


def main():
    o = pd.read_parquet(OANDA)
    idx = pd.DatetimeIndex(o.index).tz_localize("America/New_York", ambiguous="NaT", nonexistent="shift_forward")
    o = o[pd.notna(idx)]
    o.index = pd.DatetimeIndex(idx[pd.notna(idx)]).tz_convert("UTC").tz_localize(None)
    o = o[~o.index.duplicated(keep="last")].sort_index()
    assert o.index.is_unique

    # check 9 - clock: the 12:30 UTC US CPI minute must be the largest |move| of its hour on release days
    cpi = ["2023-01-12", "2023-02-14", "2024-04-10", "2024-07-11", "2025-01-15"]   # winter and summer
    for d in cpi:
        w = o.loc[f"{d} 12:00":f"{d} 12:59"]
        mv = (w["bid_close"] - w["bid_open"]).abs()
        print(f"clock check {d}: largest 1-min move at {mv.idxmax().time()} UTC ({mv.max() / PIP:.1f} pips)")

    t = build(o, "bid_open", "ask_open", "ask_high", EXTRA_PIPS)
    spread = ((at(o, "ask_open", (0, 55)) - at(o, "bid_open", (0, 55))) / PIP).reindex(t.index)
    print(f"OANDA spread at 00:55 UTC: median {spread.median():.2f} pips, p90 {spread.quantile(.9):.2f}")
    bk = report("OANDA bid/ask (naive NY stamps -> UTC)", t)
    bk[["R"]].to_csv(r"<repo>\prop_eur2k_lab\pillar2_hunt\_tokyo_fix_causal_trades.csv")

    # check 13 - independent vendor: HistData, fixed EST (UTC-5, no DST), mid prices; spread charged from OANDA median
    h = pd.read_parquet(HIST)
    h.index = pd.DatetimeIndex(h.index) + pd.Timedelta(hours=5)
    h = h[~h.index.duplicated(keep="last")].sort_index()
    th = build(h, "open", "open", "high", EXTRA_PIPS + float(spread.median()))
    report("HistData mid (EST+5h), cost = OANDA median spread + 0.5 pip", th)
    both = t.join(th, how="inner", lsuffix="_o", rsuffix="_h")
    print(f"\nvendor agreement: {len(both)} common days, corr of R = {both['R_o'].corr(both['R_h']):.3f}, "
          f"entry diff median {((both['entry_o'] - both['entry_h']).abs() / PIP).median():.2f} pips")


if __name__ == "__main__":
    sys.exit(main())
