147 lines
6.1 KiB
Python
147 lines
6.1 KiB
Python
import os
|
||
import sys
|
||
import psycopg2
|
||
from psycopg2.extras import RealDictCursor
|
||
|
||
# Path ayarları
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
|
||
from services.single_match_orchestrator import SingleMatchOrchestrator
|
||
from services.feature_enrichment import FeatureEnrichmentService
|
||
|
||
DSN = "postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db"
|
||
|
||
def run_backtest(target_date="2026-05-03"):
|
||
conn = psycopg2.connect(DSN)
|
||
cur = conn.cursor(cursor_factory=RealDictCursor)
|
||
|
||
# 1. Hedef tarihteki bitmiş maçları ve takım isimlerini getir
|
||
cur.execute("""
|
||
SELECT m.id, m.score_home, m.score_away, m.mst_utc,
|
||
t1.name as home_name, t2.name as away_name
|
||
FROM matches m
|
||
LEFT JOIN teams t1 ON m.home_team_id = t1.id
|
||
LEFT JOIN teams t2 ON m.away_team_id = t2.id
|
||
WHERE m.status IN ('FT', 'AET', 'PEN')
|
||
AND to_timestamp(m.mst_utc / 1000.0)::date = %s::date
|
||
AND m.score_home IS NOT NULL
|
||
ORDER BY m.mst_utc ASC
|
||
""", (target_date,))
|
||
matches = cur.fetchall()
|
||
|
||
if not matches:
|
||
print(f"❌ {target_date} tarihinde bitmiş maç bulunamadı.")
|
||
return
|
||
|
||
print(f"🚀 {target_date} için Orkestratör Backtesti Başlatılıyor... ({len(matches)} maç bulundu)")
|
||
print("-" * 60)
|
||
|
||
orchestrator = SingleMatchOrchestrator()
|
||
|
||
bets_placed = 0
|
||
won = 0
|
||
lost = 0
|
||
total_odds_won = 0.0
|
||
|
||
for match in matches:
|
||
# 3. Üst Akıl (Orkestratör) analizi yapar
|
||
try:
|
||
package = orchestrator.analyze_match(match['id'])
|
||
except Exception as e:
|
||
print(f"Hata ({match['id']}): {e}")
|
||
continue
|
||
|
||
if not package:
|
||
continue
|
||
|
||
package_data = package
|
||
|
||
# 4. Üst akıl bu maça bahis yapmaya karar verdi mi?
|
||
bet_advice = package_data.get("bet_advice", {})
|
||
if bet_advice.get("playable") == True:
|
||
bets_placed += 1
|
||
main_pick = package_data.get("main_pick", {})
|
||
market = main_pick.get("market")
|
||
pick = main_pick.get("pick")
|
||
odds = float(main_pick.get("odds", 0.0) or 0.0)
|
||
|
||
# Skora göre kazanıp kazanmadığını kontrol et
|
||
is_won = False
|
||
h = match['score_home']
|
||
a = match['score_away']
|
||
|
||
if market == "MS":
|
||
if pick == "1" and h > a: is_won = True
|
||
elif pick in ("X", "0") and h == a: is_won = True
|
||
elif pick == "2" and a > h: is_won = True
|
||
elif market == "OU25":
|
||
if pick == "Üst" and (h+a) > 2.5: is_won = True
|
||
elif pick == "Alt" and (h+a) < 2.5: is_won = True
|
||
elif market == "OU15":
|
||
if pick == "Üst" and (h+a) > 1.5: is_won = True
|
||
elif pick == "Alt" and (h+a) < 1.5: is_won = True
|
||
elif market == "BTTS":
|
||
if pick == "KG Var" and h > 0 and a > 0: is_won = True
|
||
elif pick == "KG Yok" and (h == 0 or a == 0): is_won = True
|
||
elif market == "DC":
|
||
if pick == "1X" and h >= a: is_won = True
|
||
elif pick == "12" and h != a: is_won = True
|
||
elif pick == "X2" and h <= a: is_won = True
|
||
|
||
if is_won:
|
||
won += 1
|
||
total_odds_won += odds
|
||
res = "✅ KAZANDI"
|
||
else:
|
||
lost += 1
|
||
res = "❌ KAYBETTİ"
|
||
|
||
print(f"[{res}] {match['home_name']} {h}-{a} {match['away_name']} | Tahmin: {market} {pick} (Oran: {odds})")
|
||
else:
|
||
main_pick = package_data.get("main_pick", {})
|
||
reasons = main_pick.get("reasons", ["Bilinmeyen Neden"]) if main_pick else ["No main pick"]
|
||
reason = " | ".join(reasons) if isinstance(reasons, list) else str(reasons)
|
||
|
||
market_board = package_data.get("market_board", {})
|
||
main_pick_market = main_pick.get('market', 'N/A') if main_pick else 'N/A'
|
||
main_pick_pick = main_pick.get('pick', 'N/A') if main_pick else 'N/A'
|
||
print(f"[PAS] {match['home_name']} {match['score_home']}-{match['score_away']} {match['away_name']} | Reddedilen: {main_pick_market} {main_pick_pick} -> Neden: {reason}")
|
||
if "market_passed_all_gates" in reason:
|
||
print(f" DEBUG: bet_advice = {bet_advice}")
|
||
|
||
v25_ms = market_board.get("MS", {}).get("probs", {})
|
||
v27_ms = {} # V27 is merged into V25 probabilities in market_board, or we don't have separate V27 access here
|
||
|
||
# Skora göre ms kontrolü
|
||
h = match['score_home']
|
||
a = match['score_away']
|
||
actual_ms = "1" if h > a else ("X" if h == a else "2")
|
||
|
||
v25_top = max(v25_ms, key=v25_ms.get) if v25_ms else "N/A"
|
||
v27_top = "N/A"
|
||
|
||
rejected_market = main_pick.get("market", "N/A") if main_pick else "N/A"
|
||
rejected_pick = main_pick.get("pick", "N/A") if main_pick else "N/A"
|
||
|
||
print(f"[PAS] {match['home_name']} {h}-{a} {match['away_name']} | Reddedilen: {rejected_market} {rejected_pick} -> Neden: {reason}")
|
||
print(f" [V25 MS Raw: {v25_top}] [Gerçek MS: {actual_ms}]")
|
||
|
||
# Sonuç Raporu
|
||
print("\n" + "=" * 60)
|
||
print(f"📊 BACKTEST SONUÇLARI ({target_date})")
|
||
print("=" * 60)
|
||
print(f"Toplam Maç Sayısı : {len(matches)}")
|
||
print(f"Oynanan Bahis Sayısı: {bets_placed} (Oynama Oranı: %{bets_placed/len(matches)*100:.1f})")
|
||
print(f"Riskli Bulunup Pas Geçilen: {len(matches) - bets_placed}")
|
||
|
||
if bets_placed > 0:
|
||
win_rate = won / bets_placed * 100
|
||
roi = ((total_odds_won - bets_placed) / bets_placed) * 100
|
||
print(f"Kazanılan : {won}")
|
||
print(f"Kaybedilen : {lost}")
|
||
print(f"İsabet Oranı : %{win_rate:.1f}")
|
||
print(f"Net Kar (ROI) : %{roi:.1f} {'📈' if roi > 0 else '📉'}")
|
||
|
||
if __name__ == "__main__":
|
||
run_backtest("2026-05-03")
|