"""Which Pinnacle feed is more ACCURATE: pinnapi's live line or API-Football's relay?

WHY THIS TEST AND NOT THE OBVIOUS ONE. The instinctive test is "do pinnapi's
signals win more bets". Measured 2026-08-08, that needs ~3,100 scored signals --
about eight months -- because the signals the two feeds disagree on sit at ~0.11
implied, where per-bet ROI stdev is ~2.84 units.

This instead scores each feed's devigged fair value against the realised result
with a Brier score, PAIRED per fixture. Two properties make it ~50x cheaper:

  1. It uses EVERY fixture (~100-200 resolve daily), not just the ~13/day where
     the signals disagree.
  2. The feeds agree most of the time, so the paired DIFFERENCE has far less
     variance than either feed's raw accuracy. Measured stdev 0.1186, so ~580
     fixtures resolves a 0.01 effect -- roughly a week.

WHAT A POSITIVE RESULT DOES AND DOES NOT MEAN. `fresh_better > 0` means pinnapi's
line predicts outcomes better. It does NOT mean switching the feed makes money:
pinnapi shifts xlow's signals toward ~0.11 long shots, and the 2026-05-23 audit
found the rule bled there (hence EDGE_UPPER_CAP at 12%). A win here justifies
RECALIBRATING the band against the fresh line and re-testing -- not flipping the
live path, which stays on API-Football so the frozen forward test keeps running.

Results are cached in research/pin_accuracy_cache.json so repeated runs do not
re-query Kalshi for fixtures already settled.

Usage:  python research\\pin_accuracy_test.py [--target N]
"""
import csv
import json
import math
import statistics
import sys
from collections import defaultdict
from pathlib import Path

sys.path.insert(0, r'E:\soccer\main')
sys.path.insert(0, r'E:\soccer\research')

import daily_signals as DS

SRC = Path(r'E:\soccer\research\pin_fresh_capture.csv')
CACHE = Path(r'E:\soccer\research\pin_accuracy_cache.json')
TARGET = 580          # from the measured paired stdev; see module docstring


def _f(x):
    try:
        return float(x)
    except (TypeError, ValueError):
        return None


def load_cache():
    try:
        return json.loads(CACHE.read_text(encoding='utf-8'))
    except Exception:
        return {}


def main():
    target = TARGET
    if '--target' in sys.argv:
        try:
            target = int(sys.argv[sys.argv.index('--target') + 1])
        except (IndexError, ValueError):
            pass
    if not SRC.exists():
        raise SystemExit(f'no capture at {SRC}')

    # Final snapshot per fixture -- the one closest to kickoff, i.e. the fair
    # value each feed would actually have been betting on.
    last = {}
    with open(SRC, encoding='utf-8') as fh:
        for r in csv.DictReader(fh):
            k = f"{r['lc']}|{r['home']}|{r['away']}|{r['game_date']}"
            if k not in last or r['ts'] > last[k]['ts']:
                last[k] = r

    cache = load_cache()
    diffs, by_lg, unresolved = [], defaultdict(list), 0
    fresh_tot = stale_tot = 0.0

    for k, r in last.items():
        fh_, fa_ = _f(r['fair_fresh_h']), _f(r['fair_fresh_a'])
        sh_, sa_ = _f(r['fair_stale_h']), _f(r['fair_stale_a'])
        if None in (fh_, fa_, sh_, sa_):
            continue
        ftr = cache.get(k)
        if ftr is None:
            lc, home, away, gd = k.split('|')
            ftr, _ = DS.fetch_result_kalshi({'div': lc, 'home': home,
                                             'away': away, 'date': gd})
            if ftr:
                cache[k] = ftr
        if not ftr:
            unresolved += 1
            continue
        yh, ya = 1.0 * (ftr == 'H'), 1.0 * (ftr == 'A')
        bf = (fh_ - yh) ** 2 + (fa_ - ya) ** 2
        bs = (sh_ - yh) ** 2 + (sa_ - ya) ** 2
        fresh_tot += bf
        stale_tot += bs
        diffs.append(bs - bf)                 # >0 => pinnapi more accurate
        by_lg[r['lc']].append(bs - bf)

    try:
        CACHE.write_text(json.dumps(cache, indent=1), encoding='utf-8')
    except OSError:
        pass

    n = len(diffs)
    print(f'PINNACLE FEED ACCURACY -- paired Brier, lower is better')
    print(f'  fixtures resolved : {n}   (target {target})')
    print(f'  still unresolved  : {unresolved}')
    if n < 10:
        print('  too few to report')
        return
    m = statistics.mean(diffs)
    sd = statistics.pstdev(diffs) or 1e-9
    se = sd / math.sqrt(n)
    z = m / se if se else 0.0
    print(f'  mean Brier (pinnapi) : {fresh_tot / n:.5f}')
    print(f'  mean Brier (API-Ftbl): {stale_tot / n:.5f}')
    print(f'  paired difference    : {m:+.5f}  (SE {se:.5f}, z {z:+.2f})')
    lo, hi = m - 1.96 * se, m + 1.96 * se
    print(f'  95% CI               : [{lo:+.5f}, {hi:+.5f}]')
    print()
    if n < target:
        need = (1.96 * sd / max(abs(m), 1e-4)) ** 2
        print(f'  NOT YET CONCLUSIVE -- {target - n} more fixtures to target; '
              f'observed effect would need n={need:,.0f}')
    elif abs(z) < 1.96:
        print('  VERDICT: no significant accuracy difference at n>=target. '
              'The relay is good enough; staleness is not distorting fair value.')
    elif m > 0:
        print('  VERDICT: pinnapi is significantly MORE accurate. Next step is to '
              'RECALIBRATE the xlow band against the fresh line and re-test -- '
              'not to switch the live feed, which would break the frozen series.')
    else:
        print('  VERDICT: pinnapi is significantly LESS accurate. Keep API-Football.')

    print()
    print('  by league (paired difference, + favours pinnapi):')
    for lg, ds in sorted(by_lg.items(), key=lambda x: -len(x[1]))[:12]:
        if len(ds) >= 3:
            print(f'    {lg:6s} n={len(ds):4d}  {statistics.mean(ds):+.5f}')


if __name__ == '__main__':
    main()
