gg
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user