#!/usr/bin/env python3
"""
oraclelab.py -- "The moves are obviously there. What am I missing?"

Three measurements:

  1. ORACLE   How much IS there, with perfect hindsight? (validates the perception)
  2. NOISE    Does a pure random walk, calibrated to the same volatility, offer the
              SAME hindsight opportunity? If yes, visual richness carries zero
              information about predictability -- the "obvious" swings are a property
              of any volatile series, not evidence of an exploitable pattern.
  3. EDGE     What directional accuracy would you need to break even, and what is
              actually achievable?
"""
import glob, os
import numpy as np
import pandas as pd

FUT = "user_data/data/krakenfutures/futures"
SEED = 20260728


def zigzag(p, thresh):
    """Perfect-hindsight swing extraction: every reversal >= thresh (fraction)."""
    piv = [0]
    direction = 0
    last = p[0]
    for i in range(1, len(p)):
        if direction >= 0 and p[i] > last:
            last = p[i];
            if direction == 0: direction = 1
            piv[-1] = i if direction == 1 else piv[-1]
        elif direction <= 0 and p[i] < last:
            last = p[i]
            if direction == 0: direction = -1
            piv[-1] = i if direction == -1 else piv[-1]
        if direction == 1 and p[i] <= last * (1 - thresh):
            piv.append(i); direction = -1; last = p[i]
        elif direction == -1 and p[i] >= last * (1 + thresh):
            piv.append(i); direction = 1; last = p[i]
    return np.array(piv)


def oracle(p, thresh, cost):
    """Trade every swing perfectly, long AND short. Returns total return, n trades."""
    piv = zigzag(np.asarray(p, float), thresh)
    if len(piv) < 2:
        return 0.0, 0
    legs = np.abs(np.diff(np.log(np.asarray(p, float)[piv])))
    return float(legs.sum() - len(legs) * cost), len(legs)


def randomwalk(p, rng):
    """Random walk with the SAME start price and the SAME per-bar return distribution
    (bootstrap resample of the actual returns -> identical volatility, fat tails,
    everything except the time-ordering)."""
    r = np.diff(np.log(np.asarray(p, float)))
    return float(p[0]) * np.exp(np.concatenate([[0], np.cumsum(rng.permutation(r))]))


def load(tf="1h", minvol=2e6):
    out = {}
    for f in sorted(glob.glob(f"{FUT}/*-{tf}-futures.feather")):
        a = os.path.basename(f).split("_")[0]
        d = pd.read_feather(f).sort_values("date").drop_duplicates("date")
        if len(d) < 3000:
            continue
        if (d["volume"] * d["close"]).median() * (24 if tf == "1h" else 1) < minvol:
            continue
        out[a] = d.reset_index(drop=True)
    return out


if __name__ == "__main__":
    rng = np.random.default_rng(SEED)
    COST = 0.0012          # 0.05% taker x2 + ~1bp slippage x2, round trip

    data = load("1h")
    print(f"{len(data)} liquid perps (>$2M/day), 1h: {', '.join(sorted(data))}\n")

    # ---------- 1 & 2: oracle on REAL vs SHUFFLED (random-walk) price ----------
    print("=" * 94)
    print("PERFECT-HINDSIGHT ORACLE:  real price  vs  the SAME returns in random order")
    print("=" * 94)
    print(f"{'pair':7s} {'swing':>6s} | {'REAL %/yr':>11s} {'trades/day':>11s} |"
          f" {'NOISE %/yr':>11s} {'trades/day':>11s} | {'real/noise':>10s}")
    print("-" * 94)
    agg = []
    for thresh in (0.01, 0.02, 0.03):
        rr = nn = rt = nt = 0.0
        for a, d in sorted(data.items()):
            p = d["close"].values
            yrs = len(p) / 8760
            g_r, n_r = oracle(p, thresh, COST)
            reps = [oracle(randomwalk(p, rng), thresh, COST) for _ in range(5)]
            g_n = np.mean([x[0] for x in reps]); n_n = np.mean([x[1] for x in reps])
            rr += g_r / yrs; nn += g_n / yrs; rt += n_r / (yrs * 365); nt += n_n / (yrs * 365)
        k = len(data)
        print(f"{'MEAN':7s} {thresh*100:5.0f}% | {rr/k*100:10.0f}% {rt/k:11.2f} |"
              f" {nn/k*100:10.0f}% {nt/k:11.2f} | {rr/max(nn,1e-9):9.2f}x")
        agg.append((thresh, rr / k, nn / k))

    # ---------- 3: break-even accuracy ----------
    print("\n" + "=" * 94)
    print("BREAK-EVEN DIRECTIONAL ACCURACY   W = 0.5 + cost/(2 x move)")
    print("=" * 94)
    print(f"  round-trip cost assumed: {COST*100:.2f}%  (Kraken futures taker x2 + slippage)")
    print(f"\n  {'target move':>12s} | {'break-even hit rate':>20s}")
    for m in (0.005, 0.01, 0.02, 0.03, 0.05):
        print(f"  {m*100:11.1f}% | {(0.5 + COST/(2*m))*100:19.1f}%")

    # ---------- 4: what accuracy is actually there? ----------
    print("\n" + "=" * 94)
    print("ACHIEVABLE ACCURACY: is the next move predictable from the last one?")
    print("=" * 94)
    print(f"{'pair':7s} | {'autocorr(1h)':>12s} {'autocorr(4h)':>12s} {'autocorr(24h)':>13s}"
          f" | {'P(up|up)':>9s} {'P(up|down)':>11s} {'spread':>7s}")
    print("-" * 94)
    ac1 = ac4 = ac24 = sp = 0.0
    for a, d in sorted(data.items()):
        r = pd.Series(np.diff(np.log(d["close"].values)))
        a1, a4, a24 = r.autocorr(1), r.autocorr(4), r.autocorr(24)
        nxt = r.shift(-1)
        pu = (nxt[r > 0] > 0).mean(); pd_ = (nxt[r < 0] > 0).mean()
        print(f"{a:7s} | {a1:+12.4f} {a4:+12.4f} {a24:+13.4f} | {pu*100:8.2f}%"
              f" {pd_*100:10.2f}% {(pu-pd_)*100:+6.2f}pp")
        ac1 += a1; ac4 += a4; ac24 += a24; sp += pu - pd_
    k = len(data)
    print("-" * 94)
    print(f"{'MEAN':7s} | {ac1/k:+12.4f} {ac4/k:+12.4f} {ac24/k:+13.4f} |"
          f" {'':9s} {'':11s} {sp/k*100:+6.2f}pp")
