318 lines
12 KiB
Python
318 lines
12 KiB
Python
"""
|
||
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ı!")
|