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

1"""Odds comparison client for comparing model probabilities vs market odds. 

2 

3Fetches market odds from supported bookmakers and compares them 

4against the TipSharks rating engine's win/place probabilities to 

5identify discrepancies. 

6""" 

7 

8from __future__ import annotations 

9 

10from decimal import Decimal 

11from typing import Any 

12 

13 

14class OddsComparisonClient: 

15 """Compares model probabilities against market odds. 

16 

17 Supports multiple bookmaker APIs and provides normalized odds 

18 in both decimal and implied probability formats. 

19 """ 

20 

21 def __init__( 

22 self, 

23 bookmaker: str = "tab_nz", 

24 api_key: str | None = None, 

25 ) -> None: 

26 """Initialize the odds comparison client. 

27 

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] = {} 

36 

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. 

42 

43 Args: 

44 race_id: The race ID. 

45 

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 

55 

56 def implied_probability( 

57 self, 

58 decimal_odds: Decimal, 

59 margin_adjust: bool = True, 

60 ) -> float: 

61 """Convert decimal odds to implied probability. 

62 

63 Args: 

64 decimal_odds: Decimal odds (e.g., 3.50). 

65 margin_adjust: If True, apply a basic overround adjustment. 

66 

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) 

73 

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. 

80 

81 Args: 

82 model_predictions: Dict of starter_id -> win_probability. 

83 market_odds: List of market odds dicts from get_race_odds. 

84 

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 

95 

96 async def clear_cache(self) -> None: 

97 """Clear the internal odds cache.""" 

98 self._odds_cache.clear()