"""
Does xlow's ACTUAL mechanism work? (read-only, trades nothing)

THE CLAIM xlow rests on -- which nothing tested before today:
  xlow does NOT require Pinnacle to be sharper than the exchanges. It requires
  that when ONE exchange is out of line with BOTH Pinnacle and the OTHER
  exchange, that exchange is the one that is wrong. Pinnacle + the agreeing
  exchange form a 2-source consensus; the outlier is the bet.

This matters because book_accuracy_compare.py found Pinnacle has no measurable
AVERAGE edge over Kalshi/Poly (paired log loss, all null). That result was read
as fatal to xlow. It is not: two forecasters can be identical on average and
still be reliably wrong in specific, identifiable configurations. Average
sharpness and outlier-reversion are different claims, and only the second one is
xlow's.

THE TEST
  For every fixture-outcome where the two exchanges disagree and the CHEAPER one
  sits below Pinnacle while the dearer one does NOT (i.e. Pinnacle sides with the
  dearer book -- exactly xlow's shape), ask: does the outcome happen at the
  CONSENSUS rate or at the OUTLIER's rate?
    - lands near consensus -> the outlier was mispriced -> the mechanism works
    - lands near the outlier -> the outlier knew something -> mechanism is dead

  Selection uses PRICES ONLY, never the result. Every book is read at the LAST
  snapshot where all three quote, so no source gets a timing advantage.

  Split at FREEZE_DATE. The [5,12) band was chosen on pre-freeze data, so
  in-sample rows are not evidence -- that mistake was made once today already.

Usage:  python research\\outlier_reversion_test.py
"""
import sys
import math

sys.path.insert(0, r'E:\soccer\research')
import book_accuracy_compare as B

FREEZE = '2026-07-05'
BANDS = [(0.02, 0.05), (0.05, 0.12), (0.12, 0.25), (0.25, 1.00)]
XLOW_BAND = (0.05, 0.12)


def triples(fx):
    """Last snapshot where pinnacle AND kalshi AND poly all quote."""
    for d in reversed(fx['snaps']):
        if 'pinnacle' in d and 'kalshi' in d and 'poly' in d:
            return d['pinnacle'], d['kalshi'], d['poly']
    return None


def collect(fixtures, keep):
    """One record per qualifying fixture-outcome. Outcome-blind selection."""
    out = []
    for fx in fixtures:
        if not keep(fx['gd'] or ''):
            continue
        t = triples(fx)
        if not t:
            continue
        pin, k, p = t
        for i in range(3):
            if pin[i] <= 0:
                continue
            cheap, dear = (k[i], p[i]) if k[i] <= p[i] else (p[i], k[i])
            if cheap >= dear:
                continue                       # exchanges agree exactly
            rel_cheap = (pin[i] - cheap) / pin[i]
            rel_dear = (pin[i] - dear) / pin[i]
            if rel_cheap <= 0:
                continue                       # cheap book not below Pinnacle
            if rel_dear >= XLOW_BAND[0]:
                continue                       # dearer book ALSO cheap -> not an outlier
            # consensus = Pinnacle and the dearer book, which agree
            consensus = (pin[i] + dear) / 2.0
            out.append(dict(rel=rel_cheap, outlier=cheap, consensus=consensus,
                            pin=pin[i], dear=dear,
                            hit=1 if B.IDX[fx['ftr']] == i else 0))
    return out


def report(rows, label):
    print(f'\n{label}')
    if not rows:
        print('  (no rows)')
        return
    print(f'  {"deviation":12s} {"n":>5s} {"outlier":>8s} {"consensus":>10s} '
          f'{"ACTUAL":>8s} {"z_vs_out":>9s} {"z_vs_cons":>10s}  who was right')
    for lo, hi in BANDS:
        s = [r for r in rows if lo <= r['rel'] < hi]
        n = len(s)
        if n < 25:
            print(f'  {lo:.2f}-{hi:.2f}    {n:5d}  (too few)')
            continue
        hits = sum(r['hit'] for r in s)
        o = [r['outlier'] for r in s]
        c = [r['consensus'] for r in s]

        def z(ps):
            var = sum(x * (1 - x) for x in ps)
            return (hits - sum(ps)) / math.sqrt(var) if var > 0 else float('nan')

        act = hits / n
        near = ('CONSENSUS' if abs(act - sum(c) / n) < abs(act - sum(o) / n)
                else 'outlier')
        star = '  <-- xlow band' if (lo, hi) == XLOW_BAND else ''
        print(f'  {lo:.2f}-{hi:.2f}    {n:5d} {sum(o)/n*100:7.2f}% '
              f'{sum(c)/n*100:9.2f}% {act*100:7.2f}% {z(o):+9.2f} {z(c):+10.2f}  '
              f'{near}{star}')


def economics(rows, label):
    """ROI of buying the outlier at its own price, mid-to-mid."""
    s = [r for r in rows if XLOW_BAND[0] <= r['rel'] < XLOW_BAND[1]]
    n = len(s)
    if n < 25:
        print(f'  {label:28s} n={n} too few')
        return
    roi = sum((1.0 / r['outlier'] - 1.0) if r['hit'] else -1.0 for r in s) / n
    print(f'  {label:28s} n={n:4d}  ROI@mid = {roi*100:+7.1f}%')


def main():
    fixtures = B.load()
    both = sum(1 for f in fixtures if triples(f))
    print(f'fixtures with Pinnacle + Kalshi + Poly at a shared snapshot: {both}')

    ins = collect(fixtures, lambda g: g <= FREEZE)
    oos = collect(fixtures, lambda g: g > FREEZE)
    allr = ins + oos

    report(allr, 'ALL DATA (in-sample + out-of-sample pooled -- context only)')
    report(ins, f'IN-SAMPLE (<= {FREEZE}) -- the band was chosen here, NOT evidence')
    report(oos, f'OUT-OF-SAMPLE (> {FREEZE}) -- this is the real test')

    print('\nECONOMICS in the xlow band (buy the outlier at its mid):')
    economics(ins, 'in-sample')
    economics(oos, 'out-of-sample')
    economics(allr, 'pooled')

    print('\nREAD: actual landing near CONSENSUS = the outlier was mispriced = the')
    print('mechanism works. Landing near the OUTLIER = the cheap book was right.')
    print('Only the out-of-sample block is evidence.')


if __name__ == '__main__':
    main()
