"""
WHY do the "unique" (one-book-only) bets win?  Decompose by the OTHER book's CLV
on the SAME side at the SAME snapshot.

A unique bet = book A fired side S (CLV_A(S) in [0.05,0.12)) while book B did NOT
fire (B has NO side in band -> so B's CLV on S is OUTSIDE the band, i.e. either):
  (i)  B_clv(S) >= 0.12  -> the other book is EVEN CHEAPER. If the edge lives here,
       "unique" is just disguised HIGH-CLV (a repackaging of "raise the band ceiling"),
       NOT a cross-book signal.
  (ii) B_clv(S) <  0.05  -> the other book sees ~NO edge / is efficient. If the edge
       lives here, it's a GENUINE disagreement signal: one venue mispriced, the other
       corrected, and the mispriced one still wins.

Also compares OWN-CLV of unique vs overlap bets (if similar, the unique edge is NOT
just higher own-CLV). Reports n / WR / ROIturn / z per sub-bucket.
Multiplicative de-vig, 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]))
    return fx

SIDX = {'home': 0, 'away': 2}
def book_mid_ask(r, book, side):
    if book == 'kalshi':
        return _f(r.get(f'kalshi_{side}_mid')), _f(r.get(f'kalshi_{side}_ask'))
    return _f(r.get(f'poly_{side}_price')), _f(r.get(f'poly_{side}_price'))

def fire(fx, book):
    """Return bet dict (incl the firing ROW + pin fair on side) + have_price flag."""
    cands = []; have = False
    for side, sidx in SIDX.items():
        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, px = book_mid_ask(r, book, side)
            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(dict(clv=clv, side=side, px=px, won=won, row=r, pf=pf)); break
    if not cands: return (None, have)
    return (max(cands, key=lambda d: d['clv']), have)

def other_clv(bet, other_book):
    """Other book's CLV on the SAME side at the firing snapshot row."""
    r, side, pf = bet['row'], bet['side'], bet['pf']
    mid, _ = book_mid_ask(r, other_book, side)
    if mid is None or not (0 < mid < 1) or not pf: return None
    return (pf - mid) / pf

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
    oc = [b['ownclv'] for b in bets if b.get('ownclv') is not None]
    return dict(n=n, wr=wins/n*100, roi=net/n*100, z=z, p=0.5*math.erfc(z/math.sqrt(2)),
                ownclv=(sum(oc)/len(oc)*100 if oc else float('nan')))

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

def main():
    fx = load()
    uniq = []; overlap = []
    for f in fx:
        k, kh = fire(f, 'kalshi'); p, ph = fire(f, 'poly')
        if not (kh and ph): continue
        if k and p:
            k['ownclv'] = k['clv']; overlap.append(k); continue
        if k and not p:
            oc = other_clv(k, 'poly');  k['ownclv'] = k['clv']; k['otherclv'] = oc; uniq.append(k)
        elif p and not k:
            oc = other_clv(p, 'kalshi'); p['ownclv'] = p['clv']; p['otherclv'] = oc; uniq.append(p)

    print(f"unique={len(uniq)}  overlap={len(overlap)}\n")

    # own-CLV: is "unique" just higher own-CLV in disguise?
    print("OWN-CLV check (is unique just higher own-CLV?):")
    line("UNIQUE bets", stat(uniq))
    line("OVERLAP bets", stat(overlap))

    # decompose unique by the OTHER book's CLV on the same side
    have_oc = [b for b in uniq if b.get('otherclv') is not None]
    no_oc   = [b for b in uniq if b.get('otherclv') is None]
    cheaper = [b for b in have_oc if b['otherclv'] >= BAND[1]]      # other EVEN cheaper
    effic   = [b for b in have_oc if b['otherclv'] < BAND[0]]       # other ~efficient
    inband  = [b for b in have_oc if BAND[0] <= b['otherclv'] < BAND[1]]  # should be ~0
    print(f"\nDecompose UNIQUE by OTHER book's CLV on the bet side "
          f"(have_other_price={len(have_oc)}, no_other_price={len(no_oc)}):")
    line("(i)  other EVEN CHEAPER (oclv>=0.12)", stat(cheaper))
    line("(ii) other EFFICIENT  (oclv<0.05)",   stat(effic))
    line("     other in-band [0.05,0.12)",      stat(inband))
    line("     other price MISSING at row",     stat(no_oc))

    # distribution of other-book CLV
    if have_oc:
        ocs = sorted(b['otherclv'] for b in have_oc)
        neg = sum(1 for x in ocs if x < 0)
        print(f"\n  other-book CLV on bet side: n={len(ocs)}  "
              f"min={ocs[0]*100:+.1f}%  median={ocs[len(ocs)//2]*100:+.1f}%  max={ocs[-1]*100:+.1f}%  "
              f"({neg} of {len(ocs)} NEGATIVE = other book ABOVE fair)")

    print("\nInterpretation key:")
    print("  edge in (i)  -> 'unique' = disguised HIGH-CLV (raise band ceiling instead)")
    print("  edge in (ii) -> GENUINE cross-book disagreement signal")

if __name__ == '__main__':
    main()
