"""
Per-league market-quality + outcome table for EVERY Kalshi league in poly_backfill.

Goal: see the full distribution of the two pre-bet quality signals (Pinnacle
overround vig, Kalshi ask-bid spread) against realized outcome, so we can judge
whether a CONTINUOUS quality gate (skip high-vig / wide-spread) is sensible --
WITHOUT cutting on which league happened to lose. Read-only.

For the [0.05,0.12) mult-de-vig Kalshi strategy (bet at ask), per league:
  n, WR%, implied%, ROIturn%, z, pinVig% (overround-1), kSprd% (ask-bid),
  entry ask, npre (snaps/fixture), poly-covered flag.
Sorted by pinVig so the quality gradient is visible.
"""
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 load():
    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)
    fx = []
    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
        has_poly = any(_f(r.get('poly_home_price')) or _f(r.get('poly_away_price'))
                       for _, r in pre)
        fx.append(dict(gd=gd, league=league, ftr=ftr,
                       pre=[r for _, r in pre], npre=len(pre), has_poly=has_poly))
    return fx

def kalshi_bet(fx):
    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'kalshi_{side}_mid')); ask = _f(r.get(f'kalshi_{side}_ask'))
            bid = _f(r.get(f'kalshi_{side}_bid'))
            if mid is None or ask is None or not (0 < mid < 1) or not (0 < ask < 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')
            vig = (1/ph + 1/pd_ + 1/pa) - 1
            spread = (ask - bid) if (bid is not None and bid > 0) else None
            cands.append(dict(clv=clv, ask=ask, won=won, vig=vig, spread=spread)); break
    if not cands: return None
    return max(cands, key=lambda d: d['clv'])

def agg(bets, npre):
    n = len(bets)
    wins = sum(b['won'] for b in bets)
    net = sum(((1-b['ask'])/b['ask']) if b['won'] else -1.0 for b in bets)
    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
    sp = [b['spread'] for b in bets if b['spread'] is not None]
    return dict(n=n, wr=wins/n*100, iwr=mu/n*100, roi=net/n*100, z=z,
                vig=sum(b['vig'] for b in bets)/n*100,
                spread=(sum(sp)/len(sp)*100 if sp else float('nan')),
                ask=sum(b['ask'] for b in bets)/n,
                clv=sum(b['clv'] for b in bets)/n*100, npre=npre)

def main():
    fx = load()
    bets_by_lg = defaultdict(list); npre_by_lg = defaultdict(list); poly_lg = {}
    for f in fx:
        b = kalshi_bet(f)
        if b:
            bets_by_lg[f['league']].append(b)
            npre_by_lg[f['league']].append(f['npre'])
            poly_lg[f['league']] = poly_lg.get(f['league'], False) or f['has_poly']

    print("Per-league Kalshi [0.05,0.12) at ask, sorted by Pinnacle vig (market quality):")
    print(f"  {'lg':6s} {'P?':>2} {'n':>4} {'WR%':>5} {'impl%':>6} {'ROI%':>7} {'z':>6} "
          f"{'pinVig%':>8} {'kSprd%':>7} {'ask':>5} {'CLV%':>5} {'npre':>5}")
    rows = []
    for lg, bets in bets_by_lg.items():
        npre = sum(npre_by_lg[lg])/len(npre_by_lg[lg])
        a = agg(bets, npre); a['lg'] = lg; a['poly'] = poly_lg[lg]
        rows.append(a)
    for a in sorted(rows, key=lambda d: d['vig']):
        pflag = 'Y' if a['poly'] else '.'
        print(f"  {a['lg']:6s} {pflag:>2} {a['n']:4d} {a['wr']:5.1f} {a['iwr']:6.1f} "
              f"{a['roi']:+7.1f} {a['z']:+6.2f} {a['vig']:8.2f} {a['spread']:7.2f} "
              f"{a['ask']:5.2f} {a['clv']:5.1f} {a['npre']:5.1f}")

    # correlation: does higher vig / wider spread predict lower per-league ROI?
    def corr(xs, ys):
        n = len(xs)
        mx, my = sum(xs)/n, sum(ys)/n
        cov = sum((x-mx)*(y-my) for x, y in zip(xs, ys))
        sx = math.sqrt(sum((x-mx)**2 for x in xs)); sy = math.sqrt(sum((y-my)**2 for y in ys))
        return cov/(sx*sy) if sx > 0 and sy > 0 else float('nan')
    big = [a for a in rows if a['n'] >= 10]
    print(f"\nAcross leagues with n>=10 ({len(big)} leagues), per-league correlations:")
    print(f"  corr(pinVig, ROI)  = {corr([a['vig'] for a in big], [a['roi'] for a in big]):+.2f}")
    sp_ok = [a for a in big if not math.isnan(a['spread'])]
    print(f"  corr(kSprd,  ROI)  = {corr([a['spread'] for a in sp_ok], [a['roi'] for a in sp_ok]):+.2f}")
    print(f"  corr(pinVig, WR-impl gap) = "
          f"{corr([a['vig'] for a in big], [a['wr']-a['iwr'] for a in big]):+.2f}")

    # bet-weighted quality buckets (continuous gate preview, NOT a league cut)
    allb = [(b, poly_lg) for lg in bets_by_lg for b in bets_by_lg[lg]]
    flat = [b for lg in bets_by_lg for b in bets_by_lg[lg]]
    print(f"\nBet-weighted by Pinnacle vig bucket (all {len(flat)} bets, continuous-gate preview):")
    print(f"  {'vig bucket':16s} {'n':>4} {'WR%':>5} {'impl%':>6} {'ROI%':>7} {'z':>6}")
    buckets = [(0,0.04),(0.04,0.05),(0.05,0.06),(0.06,0.08),(0.08,1.0)]
    for lo, hi in buckets:
        sel = [b for b in flat if lo <= b['vig'] < hi]
        if not sel:
            print(f"  [{lo:.2f},{hi:.2f})      {0:4d}"); continue
        a = agg(sel, 0)
        print(f"  [{lo:.2f},{hi:.2f})      {a['n']:4d} {a['wr']:5.1f} {a['iwr']:6.1f} "
              f"{a['roi']:+7.1f} {a['z']:+6.2f}")
    print(f"\nBet-weighted by Kalshi spread bucket:")
    print(f"  {'spread bucket':16s} {'n':>4} {'WR%':>5} {'impl%':>6} {'ROI%':>7} {'z':>6}")
    sbuckets = [(0,0.01),(0.01,0.02),(0.02,0.03),(0.03,0.05),(0.05,1.0)]
    sflat = [b for b in flat if b['spread'] is not None]
    for lo, hi in sbuckets:
        sel = [b for b in sflat if lo <= b['spread'] < hi]
        if not sel:
            print(f"  [{lo:.2f},{hi:.2f})      {0:4d}"); continue
        a = agg(sel, 0)
        print(f"  [{lo:.2f},{hi:.2f})      {a['n']:4d} {a['wr']:5.1f} {a['iwr']:6.1f} "
              f"{a['roi']:+7.1f} {a['z']:+6.2f}")

if __name__ == '__main__':
    main()
