"""
Is Poly's apparent edge over Kalshi just a LEAGUE-MIX artifact (Poly not listing the
leagues where Kalshi loses)?  Control by comparing the two books on the SAME fixtures.

The poly_backfill/*.csv rows carry BOTH kalshi_{side}_mid/ask AND poly_{side}_price, so
we can run each book's [0.05,0.12) strategy on identical fixtures. Kalshi bets at its real
ask (executable); Poly bets at mid and at mid+1x near-term half-spread (estimated ask).
Multiplicative de-vig throughout (the live default), home/away only, both-sides dedupe.

Reports:
  A. each book on its OWN full fixture set (the headline, uncontrolled comparison)
  B. MATCHED set: fixtures where BOTH books quoted -> Kalshi vs Poly head-to-head
  C. Kalshi restricted to Poly-COVERED LEAGUES (does dropping non-Poly leagues fix Kalshi?)
  D. per-league on matched fixtures: n, Kalshi ROI, Poly ROI side by side
If Poly still beats Kalshi on the MATCHED set, it is NOT a league-mix artifact.
"""
import csv, glob, os, math
from datetime import datetime
from collections import defaultdict

DIR = r"E:\soccer\poly_backfill"
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 half_spread(mid):   # near-term Poly sample (poly_spread_nearterm.py)
    if   mid < 0.05: return 0.003
    elif mid < 0.10: return 0.005
    elif mid < 0.15: return 0.005
    elif mid < 0.25: return 0.005
    elif mid < 0.35: return 0.025
    return 0.020

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]))
    return prepped

def pick(fx, pricecol, sidx_map):
    """First in-band [pin_fair-price]/pin_fair bet for a book; both-sides dedupe to higher CLV.
       Returns (clv, price_used_for_bet_dict). price columns: f'{book}'. Generic via pricecol fn."""
    return None  # placeholder (unused)

def select(prepped, book):
    """book='kalshi' (bet at ask) or 'poly_mid'/'poly_ask'. Returns list of bets w/ league."""
    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
                if book == 'kalshi':
                    mid = _f(r.get(f'kalshi_{side}_mid')); px = _f(r.get(f'kalshi_{side}_ask'))
                else:
                    mid = _f(r.get(f'poly_{side}_price'))
                    px = mid if book == 'poly_mid' else (mid + half_spread(mid) if mid else None)
                if mid is None or px is None or not (0 < mid < 1) or not (0 < px < 1): continue
                pf = fair_mult((ph, pd_, pa))[sidx]
                clv = (pf - mid) / pf if pf else None
                if clv is None or not (BAND[0] <= clv < BAND[1]): continue
                won = (fx['ftr'] == 'H' and side == 'home') or (fx['ftr'] == 'A' and side == 'away')
                cands.append((clv, px, won)); break
        if cands:
            clv, px, won = max(cands, key=lambda t: t[0])
            bets.append(dict(gd=fx['gd'], league=fx['league'], px=px, won=won,
                             key=(fx['gd'], fx['league'])))
    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['px'])/b['px']) if b['won'] else -1.0 for b in bets)
    mu = sum(b['px'] for b in bets); var = sum(b['px']*(1-b['px']) 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, roi=net/n*100, z=z, p=p)

def line(tag, s):
    if not s: print(f"  {tag:34s} (no bets)"); return
    print(f"  {tag:34s} n={s['n']:4d}  WR={s['wr']:4.1f}%  ROIturn={s['roi']:+6.1f}%  z={s['z']:+.2f}  p={s['p']:.3f}")

def main():
    prepped = load_fixtures()
    K  = select(prepped, 'kalshi')
    Pm = select(prepped, 'poly_mid')
    Pa = select(prepped, 'poly_ask')
    print(f"fixtures total={len(prepped)}\n")

    print("A. Each book on its OWN fixtures (uncontrolled):")
    line("Kalshi (at ask)", stat(K))
    line("Poly (at mid)",   stat(Pm))
    line("Poly (at est ask)", stat(Pa))

    # fixtures (gd,league) where a Kalshi bet AND a Poly bet both fired
    kkeys = {b['key'] for b in K}; pkeys = {b['key'] for b in Pm}
    matched = kkeys & pkeys
    print(f"\nB. MATCHED fixtures where BOTH books fired a bet (n_fixtures={len(matched)}):")
    line("Kalshi (at ask)  | matched",  stat([b for b in K  if b['key'] in matched]))
    line("Poly (at mid)    | matched",  stat([b for b in Pm if b['key'] in matched]))
    line("Poly (at est ask)| matched",  stat([b for b in Pa if b['key'] in matched]))

    # Kalshi restricted to leagues Poly covers
    poly_leagues = {b['league'] for b in Pm}
    print(f"\nC. Kalshi restricted to Poly-COVERED leagues ({len(poly_leagues)} leagues):")
    line("Kalshi (at ask) | poly-leagues", stat([b for b in K if b['league'] in poly_leagues]))
    line("Kalshi (at ask) | NON-poly-lg",  stat([b for b in K if b['league'] not in poly_leagues]))

    # per-league on matched fixtures
    print("\nD. Per-league on MATCHED fixtures (Kalshi ask vs Poly est-ask):")
    print(f"  {'lg':6s} {'n':>4} | {'K_ROI%':>8} {'K_z':>6} | {'P_ROI%':>8} {'P_z':>6}")
    Kby = defaultdict(list); Pby = defaultdict(list)
    for b in K:
        if b['key'] in matched: Kby[b['league']].append(b)
    for b in Pa:
        if b['key'] in matched: Pby[b['league']].append(b)
    for lg in sorted(set(Kby) | set(Pby), key=lambda l: -len(Kby.get(l, []))):
        ks, ps = stat(Kby.get(lg, [])), stat(Pby.get(lg, []))
        n = ks['n'] if ks else 0
        kroi = f"{ks['roi']:+8.1f}" if ks else "      --"
        kz   = f"{ks['z']:+6.2f}" if ks else "    --"
        proi = f"{ps['roi']:+8.1f}" if ps else "      --"
        pz   = f"{ps['z']:+6.2f}" if ps else "    --"
        print(f"  {lg:6s} {n:4d} | {kroi} {kz} | {proi} {pz}")

if __name__ == '__main__':
    main()
