Browse code

Implement functions to convert to/from different odds formats

Joseph Weston authored on 04/03/2021 06:52:24
Showing 1 changed files
... ...
@@ -1,12 +1,84 @@
1 1
 """Tools for dealing with bookmaker's odds and implied probabilities"""
2 2
 
3
+from fractions import Fraction
3 4
 from math import sqrt
4
-from typing import Sequence
5
+from typing import Optional, Sequence
5 6
 
6 7
 import scipy.optimize
7 8
 import toolz
8 9
 
9 10
 
11
+def overround(p: Sequence[float]) -> float:
12
+    """Return the overround, given a sequence of implied probabilities."""
13
+    return sum(p) - 1
14
+
15
+
16
+def from_american(odds: int) -> float:
17
+    """Return implied probability, given American odds."""
18
+    odds = round(odds)
19
+    if abs(odds) < 100:
20
+        raise ValueError(f"Invalid American odds {odds}")
21
+    if odds > 0:
22
+        return 100 / (100 + odds)
23
+    else:
24
+        odds = -odds
25
+        return odds / (100 + odds)
26
+
27
+
28
+def to_american(p: float, ndigits: Optional[int] = None) -> int:
29
+    """Return American odds, given an implied probability.
30
+
31
+    Parameters
32
+    ----------
33
+    p
34
+        Implied probability
35
+    ndigits
36
+        The number of digits to which to round the result. The default
37
+        is to round to the nearest integer (American odds are not usually
38
+        quoted to more decimal places), but this may be adjusted if
39
+        more precision is needed (and the result will be a floating
40
+        point number).
41
+    """
42
+    if p < 0.5:
43
+        a = 100 / p - 100
44
+    else:
45
+        a = -(100 * p) / (1 - p)
46
+    return round(a, ndigits)
47
+
48
+
49
+def from_euro(odds: float) -> float:
50
+    """Return implied probability, given Euro/Decimal odds."""
51
+    return 1 / odds
52
+
53
+
54
+def to_euro(p: float) -> float:
55
+    """Return Euro/Decimal odds, given an implied probability."""
56
+    return 1 / p
57
+
58
+
59
+def from_british(odds: Fraction) -> float:
60
+    """Return implied probability, given British odds."""
61
+    return float(1 / (odds + 1))
62
+
63
+
64
+def to_british(p: float, largest_denominator: int = 100) -> Fraction:
65
+    """Return British odds, given an implied probability.
66
+
67
+    Parameters
68
+    ----------
69
+    p
70
+        Implied probability
71
+    largest_denominator
72
+        The largest denominator to use when representing the result.
73
+        The smaller this number the more accuracy is lost.
74
+    """
75
+    return Fraction(1 / p - 1).limit_denominator(largest_denominator)
76
+
77
+
78
+from_decimal = from_euro
79
+to_decimal = to_euro
80
+
81
+
10 82
 # -- Adjusting implied probabilities to true probabilities
11 83
 
12 84
 _adjust_methods = {}
... ...
@@ -18,10 +90,6 @@ def add_method(name: str, f):
18 90
     return f
19 91
 
20 92
 
21
-def overround(odds: Sequence[float]) -> float:
22
-    return sum(odds) - 1
23
-
24
-
25 93
 def validate_odds(odds: Sequence[float]) -> None:
26 94
     if len(odds) < 2:
27 95
         raise ValueError(f"Expected at least 2 outcomes, but got {len(odds)}")
... ...
@@ -35,7 +103,7 @@ def validate_odds(odds: Sequence[float]) -> None:
35 103
 
36 104
 
37 105
 def adjust_odds(odds: Sequence[float], method="power") -> list[float]:
38
-    """Return true odds given bookmaker odds.
106
+    """Return fair odds given bookmaker odds.
39 107
 
40 108
     Parameters
41 109
     ----------
... ...
@@ -44,7 +112,8 @@ def adjust_odds(odds: Sequence[float], method="power") -> list[float]:
44 112
 
45 113
     The implied probabilities associated with the odds quoted by a bookmaker
46 114
     sum to a number > 1. This excess is the "overround" of the odds. There
47
-    are a number of models
115
+    are a number of models for distributing the overround among the outcomes
116
+    to recover the "fair odds" of each outcome.
48 117
 
49 118
     Notes
50 119
     -----
... ...
@@ -61,7 +130,7 @@ def adjust_odds(odds: Sequence[float], method="power") -> list[float]:
61 130
     except KeyError:
62 131
         raise ValueError(
63 132
             f"Unknown method '{method}', expected one of {list(_adjust_methods)}"
64
-        )
133
+        ) from None
65 134
     validate_odds(odds)
66 135
     return f(odds)
67 136