Spaces:
Runtime error
Runtime error
| from dataclasses import dataclass | |
| from config import settings | |
| from models.economics import coase_effective_min_edge | |
| class OutcomeValue: | |
| outcome: str | |
| model_prob: float | |
| odd: float | |
| implied_prob: float | |
| fair_odd: float | |
| expected_value: float | |
| kelly_full: float | |
| kelly_quarter: float | |
| class MatchValueReport: | |
| home_team: str | |
| away_team: str | |
| outcomes: list[OutcomeValue] | |
| best: OutcomeValue | None | |
| def _kelly_fraction(prob: float, odd: float) -> float: | |
| if odd <= 1.0: | |
| return 0.0 | |
| numerator = (prob * odd) - 1.0 | |
| denominator = odd - 1.0 | |
| if denominator <= 0: | |
| return 0.0 | |
| return max(0.0, numerator / denominator) | |
| def _sanitize_prob(prob: float) -> float: | |
| return max(0.0, min(1.0, prob)) | |
| def evaluate_outcome(outcome: str, prob: float, odd: float, *, house_prob: float | None = None) -> OutcomeValue: | |
| """Avalia EV de uma aposta. | |
| Se `house_prob` for fornecido (probabilidade real da casa, ex: generosity_prob | |
| sem margem), usa ela no lugar de `1/odd` para calcular a prob implícita. | |
| EV = P_modelo / P_casa * (1 - margem_casa) — quando `house_prob` está presente, | |
| a margem já foi removida, então EV = (P_modelo * odd_cru) - 1 ainda funciona | |
| corretamente, mas o `implied_prob` fica mais preciso para referência e o | |
| filtro de "odd suspeita" ganha uma baseline real. | |
| """ | |
| p = _sanitize_prob(prob) | |
| if house_prob is not None and house_prob > 0: | |
| implied = _sanitize_prob(house_prob) | |
| else: | |
| implied = 1.0 / odd if odd > 0 else 1.0 | |
| fair_odd = (1.0 / p) if p > 0 else 999.0 | |
| ev = (p * odd) - 1.0 if odd > 0 else -1.0 | |
| kelly = _kelly_fraction(p, odd) | |
| return OutcomeValue( | |
| outcome=outcome, | |
| model_prob=p, | |
| odd=odd, | |
| implied_prob=implied, | |
| fair_odd=fair_odd, | |
| expected_value=ev, | |
| kelly_full=kelly, | |
| kelly_quarter=kelly * 0.25, | |
| ) | |
| def evaluate_match( | |
| home_team: str, | |
| away_team: str, | |
| probabilities: dict[str, float], | |
| odds: dict[str, float], | |
| min_edge: float | None = None, | |
| ) -> MatchValueReport: | |
| threshold = coase_effective_min_edge(min_edge or settings.ev_min_edge) | |
| outcomes: list[OutcomeValue] = [] | |
| for key in ("1", "X", "2"): | |
| odd = float(odds.get(key, 0.0)) | |
| prob = float(probabilities.get(key, 0.0)) | |
| if odd <= 1.0: | |
| continue | |
| outcomes.append(evaluate_outcome(key, prob, odd)) | |
| outcomes.sort(key=lambda item: item.expected_value, reverse=True) | |
| best = outcomes[0] if outcomes and outcomes[0].expected_value >= threshold else None | |
| return MatchValueReport( | |
| home_team=home_team, | |
| away_team=away_team, | |
| outcomes=outcomes, | |
| best=best, | |
| ) | |