250 lines
8.1 KiB
Python
Executable File
250 lines
8.1 KiB
Python
Executable File
"""
|
||
Value Betting Calculator
|
||
Expected Value (EV) ve stake önerileri hesaplar.
|
||
"""
|
||
|
||
from typing import Dict, Optional
|
||
from dataclasses import dataclass
|
||
|
||
|
||
@dataclass
|
||
class ValueBet:
|
||
"""Value bet analiz sonucu"""
|
||
bet_type: str # MS_1, AU25_Üst, KG_Var
|
||
my_probability: float # Bizim tahminimiz
|
||
market_odds: float # Bahis oranı
|
||
implied_probability: float # Oranın ima ettiği olasılık
|
||
edge: float # Fark (benim tahmin - implied)
|
||
expected_value: float # EV = (prob × odds) - 1
|
||
is_value: bool # EV > threshold mı?
|
||
kelly_fraction: float # Kelly stake oranı
|
||
confidence_tier: str # "banker", "strong", "value", "skip"
|
||
|
||
def to_dict(self) -> Dict:
|
||
return {
|
||
'bet_type': self.bet_type,
|
||
'my_probability': round(self.my_probability, 4),
|
||
'market_odds': self.market_odds,
|
||
'implied_probability': round(self.implied_probability, 4),
|
||
'edge': round(self.edge, 4),
|
||
'expected_value': round(self.expected_value, 4),
|
||
'is_value': self.is_value,
|
||
'kelly_fraction': round(self.kelly_fraction, 4),
|
||
'confidence_tier': self.confidence_tier,
|
||
}
|
||
|
||
|
||
class ValueCalculator:
|
||
"""
|
||
Value Betting Calculator
|
||
|
||
Tahminleri oranlarla karşılaştırarak EV hesaplar.
|
||
"""
|
||
|
||
# Eşikler
|
||
MIN_EDGE_FOR_VALUE = 0.05 # Minimum %5 edge
|
||
MIN_EDGE_FOR_STRONG = 0.10 # %10+ edge = strong value
|
||
MIN_EDGE_FOR_BANKER = 0.15 # %15+ edge = banker
|
||
|
||
KELLY_FRACTION = 0.25 # 1/4 Kelly (güvenli)
|
||
MAX_STAKE_PERCENT = 0.10 # Maksimum bank'ın %10'u
|
||
|
||
def __init__(self):
|
||
pass
|
||
|
||
def calculate_implied_probability(self, odds: float) -> float:
|
||
"""Bahis oranından implied probability hesapla"""
|
||
if odds <= 1:
|
||
return 1.0
|
||
return 1 / odds
|
||
|
||
def calculate_ev(self, probability: float, odds: float) -> float:
|
||
"""
|
||
Expected Value hesapla.
|
||
|
||
EV = (Probability × Odds) - 1
|
||
|
||
Pozitif EV = uzun vadede kar
|
||
Negatif EV = uzun vadede zarar
|
||
"""
|
||
return (probability * odds) - 1
|
||
|
||
def calculate_kelly_stake(self, probability: float, odds: float) -> float:
|
||
"""
|
||
Kelly Criterion stake hesapla.
|
||
|
||
Kelly = (p × b - q) / b
|
||
Burada:
|
||
- p = kazanma olasılığı
|
||
- q = kaybetme olasılığı (1 - p)
|
||
- b = odds - 1 (net kar)
|
||
"""
|
||
if odds <= 1:
|
||
return 0
|
||
|
||
b = odds - 1
|
||
p = probability
|
||
q = 1 - p
|
||
|
||
kelly = (p * b - q) / b
|
||
|
||
# Negatif veya çok yüksek değerleri sınırla
|
||
kelly = max(0, min(kelly, self.MAX_STAKE_PERCENT))
|
||
|
||
# Fractional Kelly (daha güvenli)
|
||
return kelly * self.KELLY_FRACTION
|
||
|
||
def analyze_bet(self, bet_type: str, my_probability: float,
|
||
market_odds: float) -> ValueBet:
|
||
"""
|
||
Tek bir bahis için value analizi yap.
|
||
|
||
Args:
|
||
bet_type: Bahis türü (MS_1, AU25_Üst, KG_Var vb.)
|
||
my_probability: Bizim tahminimiz (0-1 arası)
|
||
market_odds: Bahis oranı
|
||
|
||
Returns:
|
||
ValueBet: Analiz sonucu
|
||
"""
|
||
if market_odds <= 1:
|
||
return ValueBet(
|
||
bet_type=bet_type,
|
||
my_probability=my_probability,
|
||
market_odds=market_odds,
|
||
implied_probability=1.0,
|
||
edge=0,
|
||
expected_value=-1,
|
||
is_value=False,
|
||
kelly_fraction=0,
|
||
confidence_tier="skip"
|
||
)
|
||
|
||
implied = self.calculate_implied_probability(market_odds)
|
||
edge = my_probability - implied
|
||
ev = self.calculate_ev(my_probability, market_odds)
|
||
kelly = self.calculate_kelly_stake(my_probability, market_odds)
|
||
|
||
# Tier belirleme
|
||
if edge >= self.MIN_EDGE_FOR_BANKER and my_probability >= 0.70:
|
||
tier = "banker"
|
||
elif edge >= self.MIN_EDGE_FOR_STRONG:
|
||
tier = "strong"
|
||
elif edge >= self.MIN_EDGE_FOR_VALUE:
|
||
tier = "value"
|
||
else:
|
||
tier = "skip"
|
||
|
||
return ValueBet(
|
||
bet_type=bet_type,
|
||
my_probability=my_probability,
|
||
market_odds=market_odds,
|
||
implied_probability=implied,
|
||
edge=edge,
|
||
expected_value=ev,
|
||
is_value=edge >= self.MIN_EDGE_FOR_VALUE,
|
||
kelly_fraction=kelly,
|
||
confidence_tier=tier
|
||
)
|
||
|
||
def analyze_match_predictions(self, predictions: Dict[str, float],
|
||
odds: Dict[str, float]) -> Dict[str, ValueBet]:
|
||
"""
|
||
Maç için tüm tahminleri analiz et.
|
||
|
||
Args:
|
||
predictions: Tahminler {'MS_1': 0.55, 'MS_X': 0.25, ...}
|
||
odds: Oranlar {'MS_1': 1.80, 'MS_X': 3.50, ...}
|
||
|
||
Returns:
|
||
Dict[str, ValueBet]: Her bahis için value analizi
|
||
"""
|
||
results = {}
|
||
|
||
for bet_type, probability in predictions.items():
|
||
if bet_type in odds and odds[bet_type] > 1:
|
||
results[bet_type] = self.analyze_bet(
|
||
bet_type=bet_type,
|
||
my_probability=probability,
|
||
market_odds=odds[bet_type]
|
||
)
|
||
|
||
return results
|
||
|
||
def get_best_value_bets(self, value_bets: Dict[str, ValueBet],
|
||
top_n: int = 3) -> list:
|
||
"""En iyi value bet'leri döndür"""
|
||
valid_bets = [vb for vb in value_bets.values() if vb.is_value]
|
||
sorted_bets = sorted(valid_bets, key=lambda x: x.expected_value, reverse=True)
|
||
return sorted_bets[:top_n]
|
||
|
||
def calculate_stake(self, value_bet: ValueBet, bankroll: float,
|
||
use_kelly: bool = True) -> float:
|
||
"""
|
||
Önerilen stake miktarını hesapla.
|
||
|
||
Args:
|
||
value_bet: Value bet analizi
|
||
bankroll: Toplam bütçe
|
||
use_kelly: Kelly criterion kullan mı?
|
||
|
||
Returns:
|
||
float: Önerilen stake miktarı
|
||
"""
|
||
if not value_bet.is_value:
|
||
return 0
|
||
|
||
if use_kelly:
|
||
return bankroll * value_bet.kelly_fraction
|
||
else:
|
||
# Tier bazlı sabit stake
|
||
tier_stakes = {
|
||
"banker": 0.05,
|
||
"strong": 0.03,
|
||
"value": 0.02,
|
||
"skip": 0
|
||
}
|
||
return bankroll * tier_stakes.get(value_bet.confidence_tier, 0)
|
||
|
||
|
||
# Singleton
|
||
_calculator = None
|
||
|
||
def get_value_calculator() -> ValueCalculator:
|
||
global _calculator
|
||
if _calculator is None:
|
||
_calculator = ValueCalculator()
|
||
return _calculator
|
||
|
||
|
||
if __name__ == "__main__":
|
||
calc = get_value_calculator()
|
||
|
||
print("\n🧪 Value Calculator Test")
|
||
print("=" * 50)
|
||
|
||
# Test senaryoları
|
||
test_cases = [
|
||
{"bet": "MS_1", "prob": 0.70, "odds": 1.60}, # High prob, low odds
|
||
{"bet": "MS_1", "prob": 0.55, "odds": 1.90}, # Medium prob, good odds
|
||
{"bet": "MS_1", "prob": 0.60, "odds": 2.10}, # VALUE!
|
||
{"bet": "AU25_Üst", "prob": 0.65, "odds": 1.85}, # VALUE!
|
||
{"bet": "KG_Var", "prob": 0.50, "odds": 1.70}, # No value
|
||
]
|
||
|
||
for tc in test_cases:
|
||
result = calc.analyze_bet(tc["bet"], tc["prob"], tc["odds"])
|
||
|
||
status_emoji = "✅" if result.is_value else "❌"
|
||
tier_emoji = {"banker": "🎯", "strong": "💪", "value": "✓", "skip": "⏭️"}
|
||
|
||
print(f"\n{status_emoji} {tc['bet']}")
|
||
print(f" Tahmin: {tc['prob']:.0%} | Oran: {tc['odds']:.2f} | Implied: {result.implied_probability:.0%}")
|
||
print(f" Edge: {result.edge:+.1%} | EV: {result.expected_value:+.1%}")
|
||
print(f" Tier: {tier_emoji.get(result.confidence_tier, '')} {result.confidence_tier.upper()}")
|
||
print(f" Kelly Stake: {result.kelly_fraction:.2%} of bankroll")
|
||
|
||
if result.is_value:
|
||
stake = calc.calculate_stake(result, 1000)
|
||
print(f" 💰 Önerilen Stake (1000 TL bank): {stake:.2f} TL")
|