""" Live Scoreboard — the single source of truth for real betting performance. ========================================================================= Reads the *forward-tracked* results in `prediction_runs` (one row per analyzed match, with the staked main pick + actual outcome + realized unit_profit) and reports what ACTUALLY happened with real money logic — NOT a backtest. Why this exists: backtests on this codebase are overfit (a paper "+32.7% ROI" strategy that the live engine never even ran). The only trustworthy number is the realized P/L recorded after matches settle. This tool surfaces it. Read-only. SELECT only. Safe to run anytime. Usage: python scripts/live_scoreboard.py python scripts/live_scoreboard.py --days 30 python scripts/live_scoreboard.py --version v28-pro-max """ from __future__ import annotations import argparse import json import os import sys from collections import defaultdict from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional # utf-8 stdout so Turkish market/league names never crash on Windows cp1252 if sys.stdout and hasattr(sys.stdout, "reconfigure"): try: sys.stdout.reconfigure(encoding="utf-8") except Exception: pass SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) AI_ENGINE_DIR = os.path.dirname(SCRIPT_DIR) sys.path.insert(0, AI_ENGINE_DIR) from data.db import get_clean_dsn # noqa: E402 import psycopg2 # noqa: E402 from psycopg2.extras import RealDictCursor # noqa: E402 ODDS_BANDS = [(0, 1.5, "<1.5"), (1.5, 2.0, "1.5-2"), (2.0, 3.0, "2-3"), (3.0, 5.0, "3-5"), (5.0, 6.0, "5-6"), (6.0, 7.5, "6-7.5"), (7.5, 999, "7.5+")] def _f(x: Any, d: Optional[float] = None) -> Optional[float]: try: return float(x) if x is not None else d except (TypeError, ValueError): return d def _parse(j: Any) -> Dict[str, Any]: if isinstance(j, str): try: return json.loads(j) except Exception: return {} return j or {} def _band(odds: Optional[float]) -> str: if odds is None: return "?" for lo, hi, name in ODDS_BANDS: if lo <= odds < hi: return name return "?" def fetch_rows(args) -> List[Dict[str, Any]]: dsn = get_clean_dsn() where = ["eventual_outcome IS NOT NULL"] params: List[Any] = [] if args.version: where.append("engine_version = %s") params.append(args.version) if args.days: cutoff = datetime.now(timezone.utc) - timedelta(days=args.days) where.append("generated_at >= %s") params.append(cutoff) sql = f""" SELECT match_id, engine_version, generated_at, eventual_outcome, unit_profit, payload_summary FROM prediction_runs WHERE {' AND '.join(where)} ORDER BY generated_at ASC """ with psycopg2.connect(dsn) as conn: with conn.cursor(cursor_factory=RealDictCursor) as cur: cur.execute(sql, params) return cur.fetchall() def distill(rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """One analytic record per run with the staked pick + realized P/L.""" out = [] for r in rows: ps = _parse(r["payload_summary"]) mp = ps.get("main_pick") or {} playable = bool(mp.get("playable")) stake = _f(mp.get("stake_units"), 0.0) or 0.0 profit = _f(r["unit_profit"], 0.0) or 0.0 outcome = str(r["eventual_outcome"] or "") staked = playable and stake > 0 # settled stake = a real bet with a win/loss (exclude NO_BET / push) settled_stake = staked and not outcome.startswith(("NO_BET", "PUSH", "VOID", "CANCEL")) out.append({ "match_id": r["match_id"], "version": r["engine_version"], "ts": r["generated_at"], "market": mp.get("market") or "?", "pick": mp.get("pick"), "odds": _f(mp.get("odds")), "stake": stake, "profit": profit, "outcome": outcome, "staked": staked, "settled_stake": settled_stake, "win": settled_stake and profit > 0, }) return out def _agg(recs: List[Dict[str, Any]]) -> Dict[str, Any]: # NOTE: recorded unit_profit is on a FLAT 1u basis (win=odds-1, loss=-1), # independent of the brain's suggested stake_units. So ROI is profit per # bet at 1u flat = profit / n. (Using stake_units as denominator is wrong: # it double-counts and produces impossible >100% losses.) s = [r for r in recs if r["settled_stake"]] n = len(s) wins = sum(1 for r in s if r["win"]) sug_stake = sum(r["stake"] for r in s) profit = sum(r["profit"] for r in s) return { "n": n, "wins": wins, "hit_pct": round(100.0 * wins / n, 1) if n else None, "sug_stake": round(sug_stake, 2), "profit": round(profit, 2), "roi_pct": round(100.0 * profit / n, 1) if n else None, # flat 1u } def _line(label: str, a: Dict[str, Any]) -> str: return (f" {label:<14} n={a['n']:>4} hit={str(a['hit_pct'] if a['hit_pct'] is not None else '-'):>5}% " f"profit={a['profit']:>8.2f}u ROI(flat1u)={str(a['roi_pct'] if a['roi_pct'] is not None else '-'):>7}%") def risk_metrics(recs: List[Dict[str, Any]]) -> Dict[str, Any]: s = [r for r in sorted(recs, key=lambda x: x["ts"]) if r["settled_stake"]] cum = 0.0 peak = 0.0 max_dd = 0.0 streak = 0 worst_streak = 0 for r in s: cum += r["profit"] peak = max(peak, cum) max_dd = min(max_dd, cum - peak) if r["profit"] <= 0: streak += 1 worst_streak = max(worst_streak, streak) else: streak = 0 return {"max_drawdown_u": round(max_dd, 2), "longest_losing_streak": worst_streak, "final_cum_u": round(cum, 2)} def main(): ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("--days", type=int, default=None, help="Only last N days") ap.add_argument("--version", help="Filter by engine_version") args = ap.parse_args() rows = fetch_rows(args) recs = distill(rows) print("=" * 74) print("LIVE SCOREBOARD — realized results from prediction_runs (NOT backtest)") print("=" * 74) if recs: lo = min(r["ts"] for r in recs).date() hi = max(r["ts"] for r in recs).date() print(f"window: {lo} .. {hi} settled runs: {len(recs)}" + (f" filter: {args.version}" if args.version else "")) print() overall = _agg(recs) print("OVERALL (staked = playable bets only)") print(_line("ALL", overall)) no_bet = sum(1 for r in recs if not r["staked"]) print(f" (analyzed {len(recs)} matches; {overall['n']} actually staked, " f"{no_bet} NO_BET)") if overall["n"]: rm = risk_metrics(recs) print(f" max drawdown: {rm['max_drawdown_u']}u " f"longest losing streak: {rm['longest_losing_streak']} " f"net: {rm['final_cum_u']}u") print() print("BY ENGINE VERSION") by_v = defaultdict(list) for r in recs: by_v[r["version"]].append(r) for v, rs in sorted(by_v.items(), key=lambda kv: -len(kv[1])): print(_line(v, _agg(rs))) print() print("BY MARKET (staked)") by_m = defaultdict(list) for r in recs: if r["settled_stake"]: by_m[r["market"]].append(r) for m, rs in sorted(by_m.items(), key=lambda kv: -len(kv[1])): print(_line(m, _agg(rs))) if not by_m: print(" (no staked settled bets in window)") print() print("BY ODDS BAND (staked)") by_b = defaultdict(list) for r in recs: if r["settled_stake"]: by_b[_band(r["odds"])].append(r) for _, _, name in ODDS_BANDS: if name in by_b: print(_line(name, _agg(by_b[name]))) print() print("WEEKLY TREND (staked)") by_w = defaultdict(list) for r in recs: if r["settled_stake"]: iso = r["ts"].isocalendar() by_w[f"{iso[0]}-W{iso[1]:02d}"].append(r) for w in sorted(by_w): a = _agg(by_w[w]) print(_line(w, a)) print() print("=" * 74) print("READ: ROI < 0 over a meaningful sample = the staked signals are not") print("profitable. 'NO_BET' rows are free (no stake). CLV is unmeasurable") print("until odds movement is captured (see scripts + odds_history fix).") print("=" * 74) if __name__ == "__main__": main()