"""
Re-run the live CLV strategy with an HONEST (power) de-vig for pin_fair, and
compare to the current MULTIPLICATIVE de-vig the pipeline ships.

The calibration study (research/devig_calibration.py) showed multiplicative
over-states longshot pin_fair by ~4% relative, which inflates measured CLV
(= (pin_fair - kalshi_mid)/pin_fair). So the live [5,12)% band is really ~[1,8)%
of honest edge. Question: does a genuinely +EV band SURVIVE de-biasing, or was
the apparent edge mostly this artifact?

Mirrors the frozen forward-test selection (forward_test_drift_momentum.build_signals
baseline path): one bet per fixture, enter at the FIRST pre-KO snapshot whose
clv_kalshi (mid-based) is in BAND, both-sides dedupe to the higher-CLV side, bet
at that snapshot's ASK, settle by ftr. NON-look-ahead. Computes both de-vig
selections from the SAME raw Pinnacle odds in the snapshots (pin_home/draw/away),
so it's an apples-to-apples swap of the fair-value model only.

Reports, for each de-vig: n, win rate, flat-stake ROI-on-turnover, and the
one-sided win-rate-vs-implied(ask) z (Poisson-binomial). Plus the cumulative-z
trajectory so we can see if the honest edge holds up as n grows.
"""
import csv, glob, os, math
from datetime import datetime
from collections import defaultdict

LIVE_DIR = r"E:\soccer\live_data"
BAND = (0.05, 0.12)

_FMTS = ['%Y-%m-%d %H:%M:%S', '%m/%d/%Y %H:%M', '%m/%d/%Y %H:%M:%S', '%Y-%m-%d %H:%M']
def _pts(s):
    for f in _FMTS:
        try: return datetime.strptime(s, f)
        except (ValueError, TypeError): pass
    return None
def _f(x):
    try: return float(x)
    except (TypeError, ValueError): return None
def _result(rows):
    for r in reversed(rows):
        v = (r.get('ftr') or '').upper().strip()
        if v in ('H', 'D', 'A'): return v
    return None

def fair_mult(o):
    r = [1.0/x for x in o]; s = sum(r)
    return [ri/s for ri in r]
def fair_power(o):
    r = [1.0/x for x in o]
    lo, hi = 0.5, 10.0
    for _ in range(60):
        k = (lo+hi)/2
        s = sum(ri**k for ri in r)
        if s > 1.0: lo = k
        else:       hi = k
    k = (lo+hi)/2
    p = [ri**k for ri in r]; s = sum(p)
    return [pi/s for pi in p]

DEVIGS = {'multiplicative': fair_mult, 'power': fair_power}

def build(devig_fn):
    """Return list of bets (sorted by game_date) under one de-vig method."""
    fixtures = defaultdict(list)
    for path in glob.glob(os.path.join(LIVE_DIR, "*.csv")):
        with open(path, newline='', encoding='utf-8') as fh:
            for r in csv.DictReader(fh):
                fixtures[(r.get('game_date'), r.get('league'),
                          r.get('home'), r.get('away'))].append(r)

    bets = []
    for (gd, league, home, away), rows in fixtures.items():
        if not gd: continue
        pre = sorted([(_pts(r['timestamp']), r) for r in rows
                      if (r.get('status') or '').upper() == 'PRE' and _pts(r['timestamp'])],
                     key=lambda t: t[0])
        if len(pre) < 2: continue
        ftr = _result(rows)
        if ftr is None: continue

        cands = []
        for side, sidx in (('home', 0), ('away', 2)):
            midk = f'kalshi_{side}_mid'; askk = f'kalshi_{side}_ask'
            ent = None
            for ts, r in pre:
                ph, pd_, pa = _f(r.get('pin_home')), _f(r.get('pin_draw')), _f(r.get('pin_away'))
                mid = _f(r.get(midk))
                if None in (ph, pd_, pa) or mid is None: continue
                if not (ph > 1 and pd_ > 1 and pa > 1): continue
                pf = devig_fn((ph, pd_, pa))[sidx]
                clv = (pf - mid) / pf if pf else None
                if clv is None or not (BAND[0] <= clv < BAND[1]): continue
                ask = _f(r.get(askk))
                if ask is None or not (0 < ask < 1): continue
                ent = (clv, ask, pf); break
            if ent:
                won = (ftr == 'H' and side == 'home') or (ftr == 'A' and side == 'away')
                cands.append(dict(gd=gd, league=league, side=side,
                                  clv=ent[0], ask=ent[1], pf=ent[2], won=won))
        if cands:
            bets.append(max(cands, key=lambda d: d['clv']))   # higher-CLV side
    bets.sort(key=lambda b: b['gd'])
    return bets

def summarize(name, bets):
    n = len(bets)
    if not n:
        print(f'  {name}: no bets'); return
    wins = sum(b['won'] for b in bets)
    # flat-stake ROI on turnover ($1 each): win -> (1-ask)/ask, lose -> -1
    net = sum(((1-b['ask'])/b['ask']) if b['won'] else -1.0 for b in bets)
    roi = net / n * 100
    # one-sided win-rate-vs-implied(ask) z, Poisson-binomial
    mu = sum(b['ask'] for b in bets)
    var = sum(b['ask']*(1-b['ask']) for b in bets)
    z = (wins - mu)/math.sqrt(var) if var > 0 else 0.0
    p = 0.5*math.erfc(z/math.sqrt(2))
    print(f'  {name:14s} n={n:4d}  WR={wins/n*100:4.1f}%  impliedWR={mu/n*100:4.1f}%  '
          f'ROIturn={roi:+6.1f}%  z={z:+.2f}  p={p:.3f}')

def ztraj(name, bets, steps=(50,100,150,200,250,300,350,400,450,500)):
    print(f'  {name} cumulative win-rate-vs-implied z:')
    out = []
    for k in steps:
        if k > len(bets): break
        sub = bets[:k]
        wins = sum(b['won'] for b in sub)
        mu = sum(b['ask'] for b in sub); var = sum(b['ask']*(1-b['ask']) for b in sub)
        z = (wins-mu)/math.sqrt(var) if var>0 else 0
        out.append(f'n={k}:z={z:+.2f}')
    out.append(f'n={len(bets)}(all):z=' + (lambda b:(lambda w,m,v:f"{(w-m)/math.sqrt(v):+.2f}" if v>0 else "0")(
        sum(x["won"] for x in b), sum(x["ask"] for x in b), sum(x["ask"]*(1-x["ask"]) for x in b)))(bets))
    print('     ' + '  '.join(out))

def main():
    print('Re-running live CLV selection under each de-vig (mid-based CLV, bet at ask):\n')
    sels = {}
    for name, fn in DEVIGS.items():
        sels[name] = build(fn)
    for name in DEVIGS:
        summarize(name, sels[name])
    print()
    for name in DEVIGS:
        ztraj(name, sels[name])
    # overlap: how much does the bet SET change?
    def keyset(bets): return {(b['gd'], b['league'], b['side']) for b in bets}
    a, b = keyset(sels['multiplicative']), keyset(sels['power'])
    print(f'\n  bet-set overlap: mult={len(a)}  power={len(b)}  shared={len(a&b)}  '
          f'mult-only={len(a-b)}  power-only={len(b-a)}')

if __name__ == '__main__':
    main()
