"""
Does the de-vig correction + band shift carry over to POLYMARKET?  Read-only.

Same study as Kalshi (research/devig_live_rerun.py + devig_band_sweep.py) but on the
Poly backfill (poly_backfill/*_polybackfill.csv, 815 fixtures w/ poly prices, 23 leagues,
2026-04-18..06-18). Poly is MID-ONLY: single poly_{side}_price, no bid/ask -> a "bet" is
priced at that mid (matches the +17.4% mid-only backtest). Shares the SAME raw Pinnacle
odds (pin_home/draw/away), so we recompute pin_fair under each de-vig identically.

Selection (NON-look-ahead): first pre-KO snapshot whose mid-CLV = (pin_fair - poly_mid)/
pin_fair is in band; both-sides dedupe to higher-CLV side; bet at that snapshot's poly mid;
settle by ftr. Reports n, WR, implied(mid) WR, flat ROIturn, win-rate-vs-implied z.
Part 1: mult vs power at the current [0.05,0.12) band. Part 2: band sweep under power
with a 60/40 temporal split (in-sample band selection -> trust the split, not headline p).
"""
import csv, glob, os, math
from datetime import datetime
from collections import defaultdict

DIR = r"E:\soccer\poly_backfill"
_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
        if sum(ri**k for ri in r) > 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]
_DEVIG = {'mult': fair_mult, 'power': fair_power}

def load_fixtures():
    fixtures = defaultdict(list)
    for path in glob.glob(os.path.join(DIR, "*_polybackfill.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)
    prepped = []
    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 not pre: continue
        ftr = _result(rows)
        if ftr is None: continue
        prepped.append(dict(gd=gd, league=league, ftr=ftr, pre=[r for _, r in pre]))
    prepped.sort(key=lambda x: x['gd'])
    return prepped

def select(prepped, devig, lo, hi):
    fn = _DEVIG[devig]; bets = []
    for fx in prepped:
        cands = []
        for side, sidx in (('home', 0), ('away', 2)):
            for r in fx['pre']:
                ph, pd_, pa = _f(r.get('pin_home')), _f(r.get('pin_draw')), _f(r.get('pin_away'))
                if None in (ph, pd_, pa) or not (ph > 1 and pd_ > 1 and pa > 1): continue
                mid = _f(r.get(f'poly_{side}_price'))
                if mid is None or not (0 < mid < 1): continue
                pf = fn((ph, pd_, pa))[sidx]
                clv = (pf - mid) / pf if pf else None
                if clv is None or not (lo <= clv < hi): continue
                won = (fx['ftr'] == 'H' and side == 'home') or (fx['ftr'] == 'A' and side == 'away')
                cands.append((clv, mid, won)); break
        if cands:
            clv, mid, won = max(cands, key=lambda t: t[0])
            bets.append(dict(gd=fx['gd'], price=mid, won=won))
    bets.sort(key=lambda b: b['gd'])
    return bets

def stat(bets):
    n = len(bets)
    if not n: return None
    wins = sum(b['won'] for b in bets)
    net = sum(((1-b['price'])/b['price']) if b['won'] else -1.0 for b in bets)
    mu = sum(b['price'] for b in bets); var = sum(b['price']*(1-b['price']) 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))
    return dict(n=n, wr=wins/n*100, iwr=mu/n*100, roi=net/n*100, z=z, p=p)

def main():
    prepped = load_fixtures()
    print(f"Loaded {len(prepped)} Poly fixtures with pre-KO snapshots.\n")

    print("PART 1 -- current band [0.05,0.12), mult vs power de-vig (mid-priced):")
    for dv in ('mult', 'power'):
        s = stat(select(prepped, dv, 0.05, 0.12))
        if s:
            print(f"  {dv:5s}  n={s['n']:4d}  WR={s['wr']:4.1f}%  impl={s['iwr']:4.1f}%  "
                  f"ROIturn={s['roi']:+6.1f}%  z={s['z']:+.2f}  p={s['p']:.3f}")

    print("\nPART 2 -- band sweep under POWER de-vig (mid-priced):")
    bands = [(0.02,0.05),(0.03,0.07),(0.05,0.12),(0.05,0.20),(0.07,0.15),
             (0.07,0.20),(0.10,0.20),(0.10,0.30),(0.12,0.30),(0.05,1.0),(0.02,1.0)]
    print(f"  {'band':14s} {'n':>4} {'WR%':>5} {'impl%':>6} {'ROIturn%':>9} {'z':>6} {'p':>6}  "
          f"| 60/40  trainROI(z)   testROI(z)")
    for lo, hi in bands:
        bets = select(prepped, 'power', lo, hi)
        s = stat(bets)
        if not s:
            print(f"  [{lo:.2f},{hi:.2f})  (no bets)"); continue
        cut = int(len(bets)*0.6)
        tr, te = stat(bets[:cut]), stat(bets[cut:])
        line = (f"  [{lo:.2f},{hi:.2f})  {s['n']:4d} {s['wr']:5.1f} {s['iwr']:6.1f} "
                f"{s['roi']:+9.1f} {s['z']:+6.2f} {s['p']:6.3f}  | ")
        if tr and te:
            line += f"tr:{tr['roi']:+6.1f}%(z{tr['z']:+.2f}) te:{te['roi']:+6.1f}%(z{te['z']:+.2f})"
        print(line)
    print("\nNote: Poly is mid-only (no ask-tax). Band selection is in-sample over 11 bands;")
    print("trust the 60/40 split over headline p.")

if __name__ == '__main__':
    main()
