Files
iddaai-be/ai-engine/scripts/strategy_generator.py
T
2026-04-22 02:17:02 +03:00

318 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Strategy Generator — Senin Excel mantığını DB üzerinde otomatize eder.
Mantık:
1. Ev sahibi takım X, evinde oran bandı Y'de oynadığında → OU1.5/OU2.5/BTTS oranları
2. Deplasman takım Z, deplasmanda oran bandı W'de oynadığında → OU1.5/OU2.5/BTTS oranları
3. İkisi de yüksekse → STRATEJİ ÜRET
Çıktı: Her maç için hangi bahis oynanabilir, neden, ve geçmiş başarı oranı
"""
import psycopg2
import pandas as pd
import numpy as np
from collections import defaultdict
from datetime import datetime
# DB connection
conn = psycopg2.connect(
host="localhost",
port=15432,
dbname="boilerplate_db",
user="suggestbet",
password="SuGGesT2026SecuRe"
)
print("=" * 70)
print(" STRATEGY GENERATOR — Veritabanından Strateji Üretimi")
print("=" * 70)
# 1. Tüm biten maçları, takım adları ve MS oranlarıyla çek
query = """
SELECT
m.id as match_id,
m.home_team_id,
m.away_team_id,
m.league_id,
m.score_home,
m.score_away,
m.mst_utc,
ht.name as home_team,
at.name as away_team,
l.name as league_name
FROM matches m
JOIN teams ht ON m.home_team_id = ht.id
JOIN teams at ON m.away_team_id = at.id
JOIN leagues l ON m.league_id = l.id
WHERE m.status = 'FT'
AND m.score_home IS NOT NULL
ORDER BY m.mst_utc ASC
"""
df = pd.read_sql(query, conn)
print(f"\nToplam biten maç: {len(df):,}")
# 2. Tüm oranları çek (MS, OU25, BTTS, OU15)
odds_query = """
SELECT
oc.match_id,
oc.name as market,
os.name as selection,
CAST(os.odd_value AS DECIMAL) as odds
FROM odd_categories oc
JOIN odd_selections os ON os.odd_category_db_id = oc.db_id
WHERE oc.name IN (
'Maç Sonucu',
'2,5 Alt/Üst',
'1,5 Alt/Üst',
'3,5 Alt/Üst',
'Karşılıklı Gol'
)
"""
odds_df = pd.read_sql(odds_query, conn)
print(f"Toplam oran kaydı: {len(odds_df):,}")
# Pivot: her maç için oranları sütunlara çevir
def get_odds(match_id, market, selection):
mask = (odds_df.match_id == match_id) & (odds_df.market == market) & (odds_df.selection == selection)
vals = odds_df.loc[mask, 'odds']
return float(vals.iloc[0]) if len(vals) > 0 else None
# Daha verimli: oran lookup dict oluştur
print("Oran lookup oluşturuluyor...")
odds_lookup = {}
for _, row in odds_df.iterrows():
key = (row.match_id, row.market, row.selection)
odds_lookup[key] = float(row.odds)
def get_o(mid, market, sel):
return odds_lookup.get((mid, market, sel))
# 3. Her maça oranları ekle
print("Maçlara oranlar ekleniyor...")
df['odds_ms_h'] = df.match_id.map(lambda x: get_o(x, 'Maç Sonucu', '1'))
df['odds_ms_a'] = df.match_id.map(lambda x: get_o(x, 'Maç Sonucu', '2'))
df['odds_ms_d'] = df.match_id.map(lambda x: get_o(x, 'Maç Sonucu', '0'))
df['odds_ou25_o'] = df.match_id.map(lambda x: get_o(x, '2,5 Alt/Üst', 'Üst'))
df['odds_ou25_u'] = df.match_id.map(lambda x: get_o(x, '2,5 Alt/Üst', 'Alt'))
df['odds_ou15_o'] = df.match_id.map(lambda x: get_o(x, '1,5 Alt/Üst', 'Üst'))
df['odds_ou15_u'] = df.match_id.map(lambda x: get_o(x, '1,5 Alt/Üst', 'Alt'))
df['odds_ou35_o'] = df.match_id.map(lambda x: get_o(x, '3,5 Alt/Üst', 'Üst'))
df['odds_ou35_u'] = df.match_id.map(lambda x: get_o(x, '3,5 Alt/Üst', 'Alt'))
df['odds_btts_y'] = df.match_id.map(lambda x: get_o(x, 'Karşılıklı Gol', 'Var'))
df['odds_btts_n'] = df.match_id.map(lambda x: get_o(x, 'Karşılıklı Gol', 'Yok'))
# Sonuç hesapla
df['total_goals'] = df.score_home + df.score_away
df['ou15'] = (df.total_goals > 1).astype(int)
df['ou25'] = (df.total_goals > 2).astype(int)
df['ou35'] = (df.total_goals > 3).astype(int)
df['btts'] = ((df.score_home > 0) & (df.score_away > 0)).astype(int)
print(f"Oranı olan maç sayısı: {df.odds_ms_h.notna().sum():,}")
# 4. ORAN BANDI fonksiyonu
def odds_band(odds):
if pd.isna(odds): return None
if odds < 1.30: return '1.00-1.30'
if odds < 1.50: return '1.30-1.50'
if odds < 1.80: return '1.50-1.80'
if odds < 2.20: return '1.80-2.20'
if odds < 2.80: return '2.20-2.80'
if odds < 4.00: return '2.80-4.00'
if odds < 6.00: return '4.00-6.00'
return '6.00+'
# 5. STRATEJİ: Expanding window — sadece geçmiş veriye bakarak tahmin
print("\n" + "=" * 70)
print(" STRATEJİ BACKTEST — Expanding Window")
print("=" * 70)
# Ev sahibi geçmişi: {team_id: {odds_band: [ou15, ou25, btts, ou35, ...]}}
home_history = defaultdict(lambda: defaultdict(list))
away_history = defaultdict(lambda: defaultdict(list))
MIN_MATCHES = 8 # Minimum geçmiş maç sayısı
TEST_PCT = 0.30 # Son %30 test
N = len(df)
test_start = int(N * (1 - TEST_PCT))
results = {
'ou15_over': [], 'ou25_over': [], 'ou35_over': [],
'btts_yes': [], 'btts_no': [],
'ou25_under': [], 'ou15_under': [],
'ms_home': []
}
for i in range(N):
row = df.iloc[i]
h_odds = row.odds_ms_h
a_odds = row.odds_ms_a
if pd.isna(h_odds) or pd.isna(a_odds):
continue
h_band = odds_band(h_odds)
a_band = odds_band(a_odds)
# TEST: sadece test bölümünde bahis yap
if i >= test_start:
h_hist = home_history[row.home_team_id][h_band]
a_hist = away_history[row.away_team_id][a_band]
if len(h_hist) >= MIN_MATCHES and len(a_hist) >= MIN_MATCHES:
# Ev sahibi bu oran bandında ne yapmış?
h_ou15 = np.mean([x[0] for x in h_hist])
h_ou25 = np.mean([x[1] for x in h_hist])
h_ou35 = np.mean([x[2] for x in h_hist])
h_btts = np.mean([x[3] for x in h_hist])
h_win = np.mean([x[4] for x in h_hist])
# Deplasman bu oran bandında ne yapmış?
a_ou15 = np.mean([x[0] for x in a_hist])
a_ou25 = np.mean([x[1] for x in a_hist])
a_ou35 = np.mean([x[2] for x in a_hist])
a_btts = np.mean([x[3] for x in a_hist])
a_loss = np.mean([x[4] for x in a_hist]) # deplasman kaybetme oranı
# KOMBİNE SİNYAL
sig_ou15 = (h_ou15 + a_ou15) / 2
sig_ou25 = (h_ou25 + a_ou25) / 2
sig_ou35 = (h_ou35 + a_ou35) / 2
sig_btts = (h_btts + a_btts) / 2
sig_hw = (h_win + a_loss) / 2 # ev kazanma + deplasman kaybetme
base = {
'match': f"{row.home_team} vs {row.away_team}",
'league': row.league_name,
'home_team': row.home_team,
'away_team': row.away_team,
'h_band': h_band,
'a_band': a_band,
'h_n': len(h_hist),
'a_n': len(a_hist),
}
# OU 1.5 OVER
if sig_ou15 >= 0.85 and row.odds_ou15_o and row.odds_ou15_o > 1.01:
results['ou15_over'].append({
**base, 'signal': sig_ou15, 'odds': row.odds_ou15_o,
'won': row.ou15 == 1, 'actual_goals': row.total_goals,
'h_sig': h_ou15, 'a_sig': a_ou15
})
# OU 2.5 OVER
if sig_ou25 >= 0.70 and row.odds_ou25_o and row.odds_ou25_o > 1.10:
results['ou25_over'].append({
**base, 'signal': sig_ou25, 'odds': row.odds_ou25_o,
'won': row.ou25 == 1, 'actual_goals': row.total_goals,
'h_sig': h_ou25, 'a_sig': a_ou25
})
# OU 3.5 OVER
if sig_ou35 >= 0.60 and row.odds_ou35_o and row.odds_ou35_o > 1.20:
results['ou35_over'].append({
**base, 'signal': sig_ou35, 'odds': row.odds_ou35_o,
'won': row.ou35 == 1, 'actual_goals': row.total_goals,
'h_sig': h_ou35, 'a_sig': a_ou35
})
# BTTS YES
if sig_btts >= 0.70 and row.odds_btts_y and row.odds_btts_y > 1.10:
results['btts_yes'].append({
**base, 'signal': sig_btts, 'odds': row.odds_btts_y,
'won': row.btts == 1, 'actual_goals': row.total_goals,
'h_sig': h_btts, 'a_sig': a_btts
})
# OU 2.5 UNDER (düşük gol beklentisi)
if sig_ou25 <= 0.30 and row.odds_ou25_u and row.odds_ou25_u > 1.10:
results['ou25_under'].append({
**base, 'signal': 1-sig_ou25, 'odds': row.odds_ou25_u,
'won': row.ou25 == 0, 'actual_goals': row.total_goals,
'h_sig': 1-h_ou25, 'a_sig': 1-a_ou25
})
# MS HOME WIN (ev sahibi kazanma)
if sig_hw >= 0.75 and row.odds_ms_h and 1.10 < row.odds_ms_h < 3.50:
results['ms_home'].append({
**base, 'signal': sig_hw, 'odds': row.odds_ms_h,
'won': row.score_home > row.score_away,
'actual_goals': row.total_goals,
'h_sig': h_win, 'a_sig': a_loss
})
# History güncelle (her zaman)
home_history[row.home_team_id][h_band].append((
row.ou15, row.ou25, row.ou35, row.btts,
int(row.score_home > row.score_away)
))
away_history[row.away_team_id][a_band].append((
row.ou15, row.ou25, row.ou35, row.btts,
int(row.score_away < row.score_home) # deplasman kaybetme
))
# 6. SONUÇLARI YAZIDIR
print(f"\nTest bölümü: son {TEST_PCT*100:.0f}% ({N - test_start:,} maç)")
print(f"Minimum geçmiş: {MIN_MATCHES} maç\n")
for market_name, bets in results.items():
if not bets:
print(f"\n {market_name}: sinyal yok")
continue
bdf = pd.DataFrame(bets)
total = len(bdf)
wins = bdf.won.sum()
hit = wins / total * 100
pnl = (bdf.won * (bdf.odds - 1) - (~bdf.won) * 1).sum()
roi = pnl / total * 100
avg_odds = bdf.odds.mean()
print(f"\n{'='*60}")
print(f" {market_name.upper()}")
print(f"{'='*60}")
print(f" Toplam bahis: {total}")
print(f" Kazanan: {wins} ({hit:.1f}%)")
print(f" Ortalama odds: {avg_odds:.2f}")
print(f" PnL: {pnl:+.1f} birim")
print(f" ROI: {roi:+.1f}%")
# Farklı sinyal eşiklerinde performans
print(f"\n Sinyal eşik analizi:")
for threshold in [0.70, 0.75, 0.80, 0.85, 0.90, 0.95]:
sub = bdf[bdf.signal >= threshold]
if len(sub) < 5: continue
w = sub.won.sum()
p = (sub.won * (sub.odds - 1) - (~sub.won) * 1).sum()
r = p / len(sub) * 100
star = ' ✅ PROFIT' if r > 0 else (' ⚖️ BE' if r > -3 else '')
print(f"{threshold:.2f}: {len(sub):5d} bahis, hit={w/len(sub)*100:.1f}%, ROI={r:+.1f}%{star}")
# En iyi 10 örnek (kazanan)
if wins > 0:
best = bdf[bdf.won].nlargest(min(5, wins), 'signal')
print(f"\n Örnek kazanan bahisler:")
for _, b in best.iterrows():
print(f" {b.home_team} vs {b.away_team} ({b.league})")
print(f" Ev {b.h_band} ({b.h_sig:.0%}) + Dep {b.a_band} ({b.a_sig:.0%}) → sinyal={b.signal:.0%}, odds={b.odds:.2f}, gol={b.actual_goals:.0f}")
# 7. ÖZET TABLO
print("\n\n" + "=" * 70)
print(" ÖZET TABLO")
print("=" * 70)
print(f"{'Market':<15} {'Bahis':>6} {'Hit':>7} {'ROI':>8} {'Avg Odds':>9}")
print("-" * 50)
for market_name, bets in results.items():
if not bets: continue
bdf = pd.DataFrame(bets)
total = len(bdf)
wins = bdf.won.sum()
hit = wins / total * 100
pnl = (bdf.won * (bdf.odds - 1) - (~bdf.won) * 1).sum()
roi = pnl / total * 100
avg_odds = bdf.odds.mean()
print(f"{market_name:<15} {total:>6} {hit:>6.1f}% {roi:>+7.1f}% {avg_odds:>8.2f}")
conn.close()
print("\n✅ Tamamlandı!")