207 lines
7.0 KiB
Python
207 lines
7.0 KiB
Python
"""
|
|
Backtest for September 13th (Top Leagues Only)
|
|
==============================================
|
|
Simulates the NEW 'Skip Logic' on matches from Sept 13, 2025.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import psycopg2
|
|
from psycopg2.extras import RealDictCursor
|
|
from datetime import datetime
|
|
|
|
# Load .env manually to ensure correct DB connection
|
|
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
sys.path.insert(0, project_root) # Add root to path if needed
|
|
|
|
def get_clean_dsn() -> str:
|
|
return "postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db"
|
|
|
|
# ─── Configuration ─────────
|
|
MIN_CONF_THRESHOLDS = {
|
|
"MS": 45.0, "DC": 40.0, "OU15": 50.0, "OU25": 45.0,
|
|
"OU35": 45.0, "BTTS": 45.0, "HT": 40.0,
|
|
}
|
|
|
|
def run_backtest():
|
|
print("🚀 Backtest: 13 Eylül 2024 - Top Leagues")
|
|
print("="*60)
|
|
|
|
# 1. Load Top Leagues
|
|
leagues_path = os.path.join(project_root, "top_leagues.json")
|
|
try:
|
|
with open(leagues_path, 'r') as f:
|
|
top_leagues = json.load(f)
|
|
# Ensure they are strings for SQL IN clause
|
|
league_ids = tuple(str(lid) for lid in top_leagues)
|
|
print(f"📋 Loaded {len(top_leagues)} top leagues.")
|
|
except Exception as e:
|
|
print(f"❌ Error loading top_leagues.json: {e}")
|
|
return
|
|
|
|
# 2. Define Date Range (Sept 13, 2024 UTC)
|
|
start_dt = datetime(2024, 9, 13, 0, 0, 0)
|
|
end_dt = datetime(2024, 9, 13, 23, 59, 59)
|
|
start_ts = int(start_dt.timestamp() * 1000)
|
|
end_ts = int(end_dt.timestamp() * 1000)
|
|
|
|
dsn = get_clean_dsn()
|
|
conn = psycopg2.connect(dsn)
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
|
|
# 3. Fetch Matches & Predictions
|
|
# We need matches that are FT and have a prediction
|
|
query = """
|
|
SELECT p.match_id, p.prediction_json,
|
|
m.score_home, m.score_away, m.status, m.league_id
|
|
FROM predictions p
|
|
JOIN matches m ON p.match_id = m.id
|
|
WHERE m.mst_utc BETWEEN %s AND %s
|
|
AND m.league_id IN %s
|
|
AND m.status = 'FT'
|
|
AND p.prediction_json IS NOT NULL
|
|
"""
|
|
|
|
try:
|
|
cur.execute(query, (start_ts, end_ts, league_ids))
|
|
rows = cur.fetchall()
|
|
except Exception as e:
|
|
print(f"❌ DB Error: {e}")
|
|
cur.close()
|
|
conn.close()
|
|
return
|
|
|
|
print(f"📊 Found {len(rows)} matches with predictions on Sept 13, 2024.")
|
|
|
|
if not rows:
|
|
print("⚠️ No predictions found for this date. The AI Engine might not have processed these historical matches yet.")
|
|
print("💡 Tip: Run the feeder or AI engine on this date range to generate predictions first.")
|
|
cur.close()
|
|
conn.close()
|
|
return
|
|
|
|
total_bets = 0
|
|
winning_bets = 0
|
|
skipped_bets = 0
|
|
total_profit = 0.0
|
|
|
|
for row in rows:
|
|
data = row['prediction_json']
|
|
if isinstance(data, str):
|
|
data = json.loads(data)
|
|
|
|
home_score = row['score_home'] or 0
|
|
away_score = row['score_away'] or 0
|
|
total_goals = home_score + away_score
|
|
|
|
# Extract Main Pick
|
|
main_pick = None
|
|
main_pick_conf = 0.0
|
|
main_pick_odds = 0.0
|
|
|
|
if "main_pick" in data and isinstance(data["main_pick"], dict):
|
|
mp = data["main_pick"]
|
|
main_pick = mp.get("pick")
|
|
main_pick_conf = mp.get("confidence", 0.0)
|
|
main_pick_odds = mp.get("odds", 0.0)
|
|
|
|
if not main_pick or not main_pick_conf:
|
|
continue
|
|
|
|
# Determine Market Type
|
|
pick_str = str(main_pick).upper()
|
|
market_type = "MS"
|
|
if "1X" in pick_str or "X2" in pick_str or "12" in pick_str: market_type = "DC"
|
|
elif "ÜST" in pick_str or "ALT" in pick_str or "OVER" in pick_str or "UNDER" in pick_str:
|
|
if "1.5" in pick_str: market_type = "OU15"
|
|
elif "3.5" in pick_str: market_type = "OU35"
|
|
else: market_type = "OU25"
|
|
elif "VAR" in pick_str or "YOK" in pick_str or "BTTS" in pick_str: market_type = "BTTS"
|
|
|
|
threshold = MIN_CONF_THRESHOLDS.get(market_type, 45.0)
|
|
|
|
# --- SKIP LOGIC ---
|
|
# 1. Confidence Gate
|
|
if main_pick_conf < threshold:
|
|
skipped_bets += 1
|
|
continue
|
|
|
|
# 2. Value Gate
|
|
if main_pick_odds > 0:
|
|
implied_prob = 1.0 / main_pick_odds
|
|
my_prob = main_pick_conf / 100.0
|
|
edge = my_prob - implied_prob
|
|
if edge < -0.03:
|
|
skipped_bets += 1
|
|
continue
|
|
|
|
# --- BET PLAYED ---
|
|
total_bets += 1
|
|
is_won = False
|
|
|
|
# Resolve Result
|
|
if market_type == "MS":
|
|
if (main_pick == "1" or main_pick == "MS 1") and home_score > away_score: is_won = True
|
|
elif (main_pick == "X" or main_pick == "MS X") and home_score == away_score: is_won = True
|
|
elif (main_pick == "2" or main_pick == "MS 2") and away_score > home_score: is_won = True
|
|
|
|
elif market_type.startswith("OU"):
|
|
line = 2.5
|
|
if "1.5" in pick_str: line = 1.5
|
|
elif "3.5" in pick_str: line = 3.5
|
|
is_over = total_goals > line
|
|
is_under = total_goals < line
|
|
if ("ÜST" in pick_str or "OVER" in pick_str) and is_over: is_won = True
|
|
elif ("ALT" in pick_str or "UNDER" in pick_str) and is_under: is_won = True
|
|
|
|
elif market_type == "BTTS":
|
|
if home_score > 0 and away_score > 0:
|
|
if "VAR" in pick_str: is_won = True
|
|
else:
|
|
if "YOK" in pick_str: is_won = True
|
|
|
|
elif market_type == "DC":
|
|
if "1X" in pick_str and home_score >= away_score: is_won = True
|
|
elif "X2" in pick_str and away_score >= home_score: is_won = True
|
|
elif "12" in pick_str and home_score != away_score: is_won = True
|
|
|
|
if is_won:
|
|
winning_bets += 1
|
|
profit = main_pick_odds - 1.0
|
|
total_profit += profit
|
|
else:
|
|
total_profit -= 1.0
|
|
|
|
# Report
|
|
print("\n" + "="*60)
|
|
print("📈 BACKTEST RESULTS: 13 EYLÜL 2025 (TOP LEAGUES)")
|
|
print("="*60)
|
|
print(f"Total Matches Analyzed: {len(rows)}")
|
|
print(f"🚫 Bets SKIPPED (Low Conf/Bad Value): {skipped_bets}")
|
|
print(f"✅ Bets PLAYED: {total_bets}")
|
|
|
|
if total_bets > 0:
|
|
win_rate = (winning_bets / total_bets) * 100
|
|
roi = (total_profit / total_bets) * 100
|
|
|
|
print(f"🏆 Winning Bets: {winning_bets}")
|
|
print(f"💀 Losing Bets: {total_bets - winning_bets}")
|
|
print("-" * 40)
|
|
print(f" Win Rate: {win_rate:.2f}%")
|
|
print(f"💰 Total Profit (Units): {total_profit:.2f}")
|
|
print(f"📊 ROI: {roi:.2f}%")
|
|
|
|
if roi > 0:
|
|
print("🟢 STRATEGY IS PROFITABLE!")
|
|
else:
|
|
print("🔴 STRATEGY IS LOSING")
|
|
else:
|
|
print("⚠️ No bets were played. Thresholds might be too high or no suitable matches found.")
|
|
|
|
cur.close()
|
|
conn.close()
|
|
|
|
if __name__ == "__main__":
|
|
run_backtest()
|