"""
Pressure-test the surviving "unique-bet" OOS result before trusting it:
  1. Split-point robustness: re-run the train/test at split = 0.4,0.5,0.6,0.7 and
     also a LAST-40%-only test, to see if the held-out edge depends on where we cut.
  2. Concentration: in the TEST half, how many distinct game-DATES and LEAGUES do the
     unique WINS come from? (a +81% driven by 3 wins on one day = noise, per prior
     round-robin/BSA lessons.)
  3. Leave-one-day-out on the full unique set: drop each game-date, recompute z, report
     the biggest single-day swing (fragility check).
Multiplicative de-vig, [0.05,0.12), shared-price fixtures, Kalshi@ask / Poly@mid.
Read-only.
"""
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 key, rows in fixtures.items():
        gd, league, home, away = key
        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
        fx.append(dict(gd=gd, league=league, ftr=ftr, pre=[r for _, r in pre]))
    fx.sort(key=lambda f: f['gd'])
    return fx

def fire(fx, book):
    cands = []; have = False
    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 mid is None or px is None or not (0 < mid < 1) or not (0 < px < 1): continue
            have = True
            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, side, px, won)); break
    if not cands: return (None, have)
    clv, side, px, won = max(cands, key=lambda t: t[0])
    return (dict(side=side, px=px, won=won, clv=clv), have)

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
    return dict(n=n, wr=wins/n*100, roi=net/n*100, z=z, p=0.5*math.erfc(z/math.sqrt(2)))

def main():
    fx = load()
    uniq = []   # unique (one-book-only) bets, each tagged gd/league
    for f in fx:
        k, kh = fire(f, 'kalshi'); p, ph = fire(f, 'poly')
        if not (kh and ph): continue
        if k and not p: uniq.append(dict(gd=f['gd'], league=f['league'], book='K', **k))
        elif p and not k: uniq.append(dict(gd=f['gd'], league=f['league'], book='P', **p))
    uniq.sort(key=lambda b: b['gd'])
    print(f"total unique (one-book-only) bets = {len(uniq)}\n")

    # 1) split-point robustness
    print("1) Split-point robustness (TEST = held-out tail):")
    print(f"   {'split':>6} {'trN':>4} {'trROI':>7} {'trz':>6} | {'teN':>4} {'teROI':>7} {'tez':>6} {'tep':>6}")
    for sp in (0.40, 0.50, 0.60, 0.70):
        cut = int(len(uniq)*sp); tr, te = uniq[:cut], uniq[cut:]
        st, se = stat(tr), stat(te)
        print(f"   {sp:6.0%} {st['n']:4d} {st['roi']:+7.1f} {st['z']:+6.2f} | "
              f"{se['n']:4d} {se['roi']:+7.1f} {se['z']:+6.2f} {se['p']:6.3f}")

    # 2) concentration in the 60/40 test half
    cut = int(len(uniq)*0.60); te = uniq[cut:]
    print(f"\n2) Concentration of the 60/40 TEST half (n={len(te)}):")
    bydate = defaultdict(list); bylg = defaultdict(list)
    for b in te: bydate[b['gd']].append(b); bylg[b['league']].append(b)
    wins = [b for b in te if b['won']]
    wdates = defaultdict(int)
    for b in wins: wdates[b['gd']] += 1
    print(f"   wins={len(wins)} across {len(wdates)} distinct dates; "
          f"bets across {len(bydate)} dates, {len(bylg)} leagues")
    top = sorted(wdates.items(), key=lambda kv: -kv[1])[:5]
    print(f"   top win-dates: {top}")
    print(f"   net ROIturn if drop single best win-date:")
    for d, _ in top[:1]:
        rest = [b for b in te if b['gd'] != d]
        s = stat(rest)
        print(f"     drop {d}: n={s['n']} ROIturn={s['roi']:+.1f}% z={s['z']:+.2f} p={s['p']:.3f}")

    # 3) leave-one-day-out on FULL unique set
    print(f"\n3) Leave-one-DAY-out fragility (full unique set, n={len(uniq)}):")
    base = stat(uniq)
    dates = sorted({b['gd'] for b in uniq})
    swings = []
    for d in dates:
        s = stat([b for b in uniq if b['gd'] != d])
        swings.append((s['z'] - base['z'], d, s['z'], s['n']))
    swings.sort()
    print(f"   full-set: n={base['n']} ROIturn={base['roi']:+.1f}% z={base['z']:+.2f} p={base['p']:.3f}")
    worst = swings[0]   # most negative => that day was propping it up
    print(f"   biggest DROP in z from removing one day: {worst[0]:+.2f} "
          f"(remove {worst[1]} -> z {worst[2]:+.2f}, n={worst[3]})")
    n_pos = sum(1 for s in swings if s[2] > 1.64)
    print(f"   days where z stays > 1.64 after removal: {n_pos}/{len(dates)}")

if __name__ == '__main__':
    main()
