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