This commit is contained in:
2026-04-21 16:53:56 +03:00
parent 1346924387
commit 2ccd6831eb
26 changed files with 430403 additions and 3 deletions
+94
View File
@@ -0,0 +1,94 @@
from __future__ import annotations
import json
import sys
from pathlib import Path
import psycopg2
from psycopg2.extras import RealDictCursor
AI_ENGINE_DIR = Path(__file__).resolve().parents[1]
if str(AI_ENGINE_DIR) not in sys.path:
sys.path.insert(0, str(AI_ENGINE_DIR))
from services.single_match_orchestrator import SingleMatchOrchestrator
def _resolve_dsn() -> str:
env_path = AI_ENGINE_DIR / ".env"
if env_path.exists():
for line in env_path.read_text(encoding="utf-8").splitlines():
if line.startswith("DATABASE_URL="):
return line.split("=", 1)[1].strip().split("?schema=")[0]
raise SystemExit("DATABASE_URL not found in ai-engine/.env")
def _fetch_matches(dsn: str, limit: int = 60) -> list[str]:
query = """
SELECT m.id
FROM matches m
WHERE m.status = 'FT'
AND m.sport = 'football'
AND m.score_home IS NOT NULL
AND m.score_away IS NOT NULL
ORDER BY m.mst_utc DESC
LIMIT %s
"""
with psycopg2.connect(dsn) as conn:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(query, (limit,))
return [str(row["id"]) for row in cur.fetchall()]
def _score_prediction(package: dict) -> dict[str, float]:
rows = package.get("bet_summary", []) or []
playable = [row for row in rows if row.get("playable")]
return {
"playable_count": float(len(playable)),
"avg_edge": round(
sum(float(row.get("ev_edge", 0.0)) for row in playable) / len(playable),
4,
)
if playable
else 0.0,
"avg_confidence": round(
sum(float(row.get("calibrated_confidence", 0.0)) for row in playable)
/ len(playable),
2,
)
if playable
else 0.0,
}
def main() -> None:
dsn = _resolve_dsn()
match_ids = _fetch_matches(dsn)
orchestrator = SingleMatchOrchestrator()
results: list[dict[str, object]] = []
for match_id in match_ids:
orchestrator.engine_mode = "v25"
v25 = orchestrator.analyze_match(match_id)
orchestrator.engine_mode = "v26"
v26 = orchestrator.analyze_match(match_id)
if not v25 or not v26:
continue
results.append(
{
"match_id": match_id,
"v25": _score_prediction(v25),
"v26": _score_prediction(v26),
"v25_main": (v25.get("main_pick") or {}).get("pick"),
"v26_main": (v26.get("main_pick") or {}).get("pick"),
}
)
out_path = AI_ENGINE_DIR / "reports" / "backtest_v26_shadow.json"
out_path.write_text(json.dumps(results, indent=2), encoding="utf-8")
print(f"[OK] Shadow backtest summary written to {out_path}")
if __name__ == "__main__":
main()