"""
Is the unique-bet edge SYMMETRIC across books, or only one direction?
Two directions of a "unique" (one-book-only [0.05,0.12)) bet:
  A. KALSHI-only fires -> bet KALSHI (cheap); POLY confirms Pinnacle's fair.
  B. POLY-only  fires -> bet POLY  (cheap); KALSHI confirms Pinnacle's fair.
If only A profits -> it's "Poly/Pinnacle agreement flags a mispriced Kalshi".
If only B profits -> it's "Kalshi/Pinnacle agreement flags a mispriced Poly".
If BOTH profit -> the mechanism (2nd book validates the fair) is genuinely symmetric.

Executable pricing: Kalshi @ ask; Poly @ est-ask (mid + near-term half-spread). Also
shown restricted to the clean mechanism bucket (other book ~efficient, its CLV<0.05).
Multiplicative de-vig, shared-price fixtures. 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 half_spread(mid):
    if   mid < 0.05: return 0.003
    elif mid < 0.25: return 0.005
    elif mid < 0.35: return 0.025
    return 0.020

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 mid_of(r, book, side):
    if book == 'kalshi': return _f(r.get(f'kalshi_{side}_mid'))
    return _f(r.get(f'poly_{side}_price'))
def exec_px(r, book, side):
    if book == 'kalshi': return _f(r.get(f'kalshi_{side}_ask'))
    m = _f(r.get(f'poly_{side}_price'));  return (m + half_spread(m)) if m is not None else None

def fire(fx, book):
    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 = mid_of(r, book, side); px = exec_px(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, px=px, won=won, row=r, side=side, pf=pf)); break
    if not cands: return (None, have)
    return (max(cands, key=lambda d: d['clv']), have)

def other_clv(bet, other_book):
    mid = mid_of(bet['row'], other_book, bet['side'])
    if mid is None or not (0 < mid < 1) or not bet['pf']: return None
    return (bet['pf'] - mid) / bet['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
    return dict(n=n, wr=wins/n*100, iwr=mu/n*100, roi=net/n*100, z=z, p=0.5*math.erfc(z/math.sqrt(2)))

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

def main():
    fx = load()
    konly, ponly = [], []
    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: k['oclv'] = other_clv(k, 'poly');   konly.append(k)
        elif p and not k: p['oclv'] = other_clv(p, 'kalshi'); ponly.append(p)

    print(f"Kalshi-only={len(konly)}  Poly-only={len(ponly)}  (executable: K@ask, P@est-ask)\n")
    print("ALL unique bets, by direction:")
    line("A. bet KALSHI  (Poly confirms Pinnacle)", stat(konly))
    line("B. bet POLY    (Kalshi confirms Pinnacle)", stat(ponly))

    # clean mechanism bucket: the confirming book is ~efficient (its CLV<0.05)
    ke = [b for b in konly if b['oclv'] is not None and b['oclv'] < BAND[0]]
    pe = [b for b in ponly if b['oclv'] is not None and b['oclv'] < BAND[0]]
    print("\nRestricted to clean mechanism (confirming book CLV<0.05 = agrees w/ Pinnacle):")
    line("A. bet KALSHI | Poly efficient", stat(ke))
    line("B. bet POLY   | Kalshi efficient", stat(pe))

if __name__ == '__main__':
    main()
