gg
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
"""
|
||||
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ı!")
|
||||
Reference in New Issue
Block a user