"""
Does xlow survive Kalshi-only execution (Polymarket geo-restricted)?  YES,
if you keep Poly as the confirming SIGNAL and reroute execution to Kalshi
for every bet -- NOT if you only take the bets where Kalshi was already
the cheap leg.

Question (2026-07-13): user is geo-restricted from trading Polymarket. Two
readings tested:

  A) SKIP Poly-cheap signals entirely (only act when Kalshi is the cheap leg)
  B) KEEP all signals (Poly still just watched for confirmation, never
     traded) but REROUTE every execution to Kalshi's ask, even on the 136
     bets where Poly was actually the cheaper book.

RESULTS on poly_backfill (286 strict-xlow bets, same rule/band as the live
forward tracker):
  Reference -- both books tradable (design as frozen):
    n=286  +24.7% gross ROI  z+2.59 p=0.005  fee-adj net (Kalshi legs only,
    Poly fee-free) +19.3%  bootstrap 90% CI [+8.3%,+42.8%] P(>0)=100%

  A) SKIP Poly-cheap (Kalshi-only OPPORTUNITY SET, n=150, 52% of signals):
    +29.2% gross -> +23.7% fee-adj net.  z+1.72 p=0.043.  Per-bet edge
    actually similar-or-better than blended (Kalshi legs always ran hotter
    than Poly legs individually) but n HALVES -> the n>=100 forward
    checkpoint takes ~2x as long to reach at current stamp rate.

  B) REROUTE Poly-cheap to Kalshi execution (n=286, FULL sample kept):
    +23.2% gross -> +17.8% fee-adj net.  z+2.41 p=0.008.  bootstrap 90% CI
    [+7.3%,+40.5%] P(>0)=99%.  Avg price markup paying Kalshi instead of
    Poly on the 136 rerouted bets: only +0.83c (Kalshi, as the "confirming"
    book, sits near fair -- not cheap, but not wildly expensive either; it
    was never missing a tradeable price on those 136 bets: 0/136).
    Isolating just the 136 rerouted bets: TRAIN -4.4% / TEST +47.4% (noisier,
    front-loaded into the back half) -- but pooled with the 150 Kalshi-
    native bets this barely moves the total (TRAIN +11.9%->+10.7%).

VERDICT: (B) is the right answer for geo-restricted execution. Keep watching
ALL xlow alerts regardless of which book the signal names, execute every one
at Kalshi's current ask. Costs ~1-2pp of ROI and a touch of significance
(p .005->.008) but loses ZERO sample size and stays clearly profitable/
significant/robust. (A) is dominated -- same downside, worse (halved n,
slower path to the n>=100 forward-validation checkpoint).

Read-only, backtest only. Mirrors kalshi_poly_pure_deviation.py conventions.
"""
import csv, glob, math, random
from datetime import datetime
from collections import defaultdict

random.seed(5)
DIR = r"E:\soccer\poly_backfill"
LO, HI = 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(f"{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('home'), r.get('away'))].append(r)
    out = []
    for key, rows in fixtures.items():
        gd = key[0]
        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])
        ftr = _result(rows)
        if pre and ftr:
            out.append(dict(gd=gd, ftr=ftr, pre=[r for _, r in pre]))
    out.sort(key=lambda f: f['gd'])
    return out

def xlow(fx):
    """Strict xlow (same as forward_test_unique_book.fire_xlow). Also records
    the Kalshi ask for the picked side at the firing snapshot regardless of
    which book qualified, so rerouted-execution scoring is possible."""
    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
        pf = fair_mult((ph, pd_, pa)); cands = []
        for side, si in (('home', 0), ('away', 2)):
            km = _f(r.get(f'kalshi_{side}_mid')); ka = _f(r.get(f'kalshi_{side}_ask'))
            pm = _f(r.get(f'poly_{side}_price'))
            if km is None or pm is None or not (0 < km < 1 and 0 < pm < 1): continue
            if km <= pm: book, mid, px, other = 'kalshi', km, (ka if ka and 0 < ka < 1 else km), pm
            else:        book, mid, px, other = 'poly', pm, pm + half_spread(pm), km
            clv = (pf[si] - mid) / pf[si]; oclv = (pf[si] - other) / pf[si]
            if not (mid < other and LO <= clv < HI): continue
            if LO <= oclv < HI: continue
            won = (fx['ftr'] == 'H' and side == 'home') or (fx['ftr'] == 'A' and side == 'away')
            kalshi_ask_here = ka if ka and 0 < ka < 1 else km
            cands.append(dict(clv=clv, px=px, book=book, won=won, gd=fx['gd'], kalshi_ask=kalshi_ask_here))
        if cands: return max(cands, key=lambda d: d['clv'])
    return None

def metrics(items, fee_adj=True):
    n = len(items)
    if n < 5: return None
    w = sum(b['won'] for b in items)
    net = sum(((1 - b['px']) / b['px']) if b['won'] else -1 for b in items)
    fees = sum(0.07 * (1 - b['px']) for b in items) if fee_adj else 0
    mu = sum(b['px'] for b in items); var = sum(b['px'] * (1 - b['px']) for b in items)
    z = (w - mu) / math.sqrt(var) if var > 0 else 0
    p = 0.5 * math.erfc(z / math.sqrt(2))
    cut = int(n * 0.6)
    def roi(xs): return sum((((1 - b['px']) / b['px']) if b['won'] else -1) for b in xs) / len(xs) * 100 if xs else 0
    days = defaultdict(list)
    for b in items: days[b['gd']].append(b)
    dl = list(days.values()); rois = []
    for _ in range(2000):
        pool = []
        for _ in range(len(dl)): pool.extend(random.choice(dl))
        rois.append(roi(pool))
    rois.sort()
    return dict(n=n, w=w, wr=w/n*100, iwr=mu/n*100, roi=net/n*100, net=net,
                net_fee=(net-fees)/n*100, z=z, p=p,
                train=roi(items[:cut]), test=roi(items[cut:]),
                boot_med=rois[len(rois)//2], boot_lo=rois[int(.05*len(rois))],
                boot_hi=rois[int(.95*len(rois))], ppos=sum(1 for x in rois if x > 0)/len(rois)*100)

def line(label, m):
    if not m: print(f"  {label}: (n<5)"); return
    print(f"\n--- {label} ---")
    print(f"  n={m['n']}  {m['w']}W-{m['n']-m['w']}L  WR={m['wr']:.1f}% (impl {m['iwr']:.1f}%, edge {m['wr']-m['iwr']:+.1f}pp)")
    print(f"  gross ROI={m['roi']:+.1f}%  net={m['net']:+.1f}u  |  fee-adj NET={m['net_fee']:+.1f}%")
    print(f"  z={m['z']:+.2f}  p={m['p']:.4f}  TRAIN={m['train']:+.1f}%  TEST={m['test']:+.1f}%")
    print(f"  bootstrap: median={m['boot_med']:+.1f}%  90%CI=[{m['boot_lo']:+.1f}%,{m['boot_hi']:+.1f}%]  P(>0)={m['ppos']:.0f}%")

def main():
    F = load()
    bets = [b for b in (xlow(f) for f in F) if b]
    bets.sort(key=lambda b: b['gd'])
    kalshi_native = [b for b in bets if b['book'] == 'kalshi']
    poly_orig = [b for b in bets if b['book'] == 'poly']
    poly_rerouted = [dict(b, px=b['kalshi_ask']) for b in poly_orig]
    all_rerouted = sorted(kalshi_native + poly_rerouted, key=lambda b: b['gd'])

    print(f"Total strict xlow universe: {len(bets)} bets\n")
    line("REFERENCE: both books tradable (design as frozen)", metrics(bets, fee_adj=False))
    line("A) SKIP Poly-cheap: Kalshi-only opportunity set", metrics(kalshi_native))
    line("B1) the 136 Poly-cheap bets, rerouted to Kalshi ask (isolated)", metrics(poly_rerouted))
    line("B) FULL 286, all rerouted to Kalshi execution (pooled)", metrics(all_rerouted))

    diffs = [new['px'] - old['px'] for old, new in zip(poly_orig, poly_rerouted)]
    print(f"\navg price markup paying Kalshi instead of Poly on rerouted bets: "
          f"+{sum(diffs)/len(diffs)*100:.2f}c")
    missing = sum(1 for b in poly_orig if not (b['kalshi_ask'] and 0 < b['kalshi_ask'] < 1))
    print(f"rerouted bets with NO tradeable Kalshi price: {missing} of {len(poly_orig)}")

if __name__ == '__main__':
    main()
