Coverage for packages / betting / odds_client.py: 0%
16 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-08 08:14 +1200
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-08 08:14 +1200
1"""Odds comparison client for comparing model probabilities vs market odds.
3Fetches market odds from supported bookmakers and compares them
4against the TipSharks rating engine's win/place probabilities to
5identify discrepancies.
6"""
8from __future__ import annotations
10from decimal import Decimal
11from typing import Any
14class OddsComparisonClient:
15 """Compares model probabilities against market odds.
17 Supports multiple bookmaker APIs and provides normalized odds
18 in both decimal and implied probability formats.
19 """
21 def __init__(
22 self,
23 bookmaker: str = "tab_nz",
24 api_key: str | None = None,
25 ) -> None:
26 """Initialize the odds comparison client.
28 Args:
29 bookmaker: Target bookmaker identifier (e.g., "tab_nz",
30 "bet365", "sportsbet").
31 api_key: Optional API key for bookmaker odds feed.
32 """
33 self.bookmaker = bookmaker
34 self.api_key = api_key
35 self._odds_cache: dict[str, Any] = {}
37 async def get_race_odds(
38 self,
39 race_id: int,
40 ) -> list[dict[str, Any]]:
41 """Fetch current market odds for all runners in a race.
43 Args:
44 race_id: The race ID.
46 Returns:
47 List of odds dicts with keys:
48 - runner_name: str
49 - win_odds_decimal: Decimal
50 - win_probability: float (1 / odds, adjusted for margin)
51 - place_odds: Decimal | None
52 - last_updated: str (ISO datetime)
53 """
54 raise NotImplementedError
56 def implied_probability(
57 self,
58 decimal_odds: Decimal,
59 margin_adjust: bool = True,
60 ) -> float:
61 """Convert decimal odds to implied probability.
63 Args:
64 decimal_odds: Decimal odds (e.g., 3.50).
65 margin_adjust: If True, apply a basic overround adjustment.
67 Returns:
68 Implied probability between 0 and 1.
69 """
70 if margin_adjust:
71 return float(Decimal("1") / decimal_odds)
72 return float(Decimal("1") / decimal_odds)
74 def compare_to_model(
75 self,
76 model_predictions: dict[int, float],
77 market_odds: list[dict[str, Any]],
78 ) -> list[dict[str, Any]]:
79 """Compare model probabilities to market-implied probabilities.
81 Args:
82 model_predictions: Dict of starter_id -> win_probability.
83 market_odds: List of market odds dicts from get_race_odds.
85 Returns:
86 List of comparison dicts with keys:
87 - starter_id: int | None
88 - runner_name: str
89 - model_prob: float
90 - market_prob: float
91 - edge: float (model - market, positive = value)
92 - kelly_fraction: float | None
93 """
94 raise NotImplementedError
96 async def clear_cache(self) -> None:
97 """Clear the internal odds cache."""
98 self._odds_cache.clear()