gg3
Deploy Iddaai Backend / build-and-deploy (push) Successful in 35s

This commit is contained in:
2026-06-05 00:36:24 +03:00
parent b9700f9fda
commit 9e41407cb5
10 changed files with 1683 additions and 0 deletions
+253
View File
@@ -0,0 +1,253 @@
"""
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()