keeks 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
keeks/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """
2
+ Keeks: A Python package for simulating and evaluating betting strategies.
3
+
4
+ Keeks provides tools for implementing and testing various betting strategies,
5
+ particularly focusing on the Kelly Criterion and its variants. The package
6
+ includes:
7
+
8
+ - Bankroll management: Track and manage funds for betting
9
+ - Binary betting strategies: Implementations of Kelly Criterion and other strategies
10
+ - Simulators: Tools to evaluate strategies under different conditions
11
+
12
+ The package is designed for educational purposes and to help understand
13
+ optimal betting strategies in various scenarios.
14
+ """
keeks/bankroll.py ADDED
@@ -0,0 +1,175 @@
1
+ import matplotlib.pyplot as plt
2
+
3
+ from keeks.utils import RuinError
4
+
5
+
6
+ class BankRoll:
7
+ """
8
+ A class representing a bankroll for betting or investment strategies.
9
+
10
+ The BankRoll class manages funds, tracks history, and enforces drawdown limits
11
+ to prevent catastrophic losses. It provides methods for depositing and withdrawing
12
+ funds, as well as visualizing the bankroll history.
13
+
14
+ Parameters
15
+ ----------
16
+ initial_funds : float, default=0.0
17
+ The starting amount of money in the bankroll.
18
+ percent_bettable : float, default=1.0
19
+ The percentage of total funds that can be used for betting (0.0 to 1.0).
20
+ max_draw_down : float, default=0.3
21
+ The maximum percentage of funds that can be lost in a single withdrawal (0.0 to 1.0).
22
+ If None, no drawdown limit is enforced.
23
+ verbose : int, default=0
24
+ Controls the verbosity level of the bankroll operations.
25
+
26
+ Attributes
27
+ ----------
28
+ history : list
29
+ A list tracking the total funds after each transaction.
30
+ """
31
+
32
+ def __init__(
33
+ self, initial_funds=0.0, percent_bettable=1.0, max_draw_down=0.3, verbose=0
34
+ ):
35
+ self._bank = initial_funds
36
+ self.percent_bettable = percent_bettable
37
+ self.max_draw_down = max_draw_down
38
+ self.verbose = verbose
39
+ self.history = [initial_funds]
40
+
41
+ def update_history(self):
42
+ """
43
+ Update the history list with the current total funds.
44
+
45
+ This method is called automatically after deposits and withdrawals.
46
+ """
47
+ self.history.append(self.total_funds)
48
+
49
+ @property
50
+ def bettable_funds(self):
51
+ """
52
+ Calculate the amount of funds available for betting.
53
+
54
+ Returns
55
+ -------
56
+ float
57
+ The amount of funds available for betting, calculated as
58
+ total funds multiplied by the percent_bettable parameter.
59
+ """
60
+ return round(self._bank * self.percent_bettable, 2)
61
+
62
+ @property
63
+ def total_funds(self):
64
+ """
65
+ Get the total amount of funds in the bankroll.
66
+
67
+ Returns
68
+ -------
69
+ float
70
+ The total amount of funds, rounded to 2 decimal places.
71
+ """
72
+ return round(self._bank, 2)
73
+
74
+ def deposit(self, amt):
75
+ """
76
+ Add funds to the bankroll.
77
+
78
+ Parameters
79
+ ----------
80
+ amt : float
81
+ The amount to deposit into the bankroll.
82
+ """
83
+ self._bank += amt
84
+ self.update_history()
85
+
86
+ def withdraw(self, amt):
87
+ """
88
+ Remove funds from the bankroll.
89
+
90
+ This method enforces the max_draw_down limit if set, raising a RuinError
91
+ if the withdrawal would exceed the allowed drawdown.
92
+
93
+ Parameters
94
+ ----------
95
+ amt : float
96
+ The amount to withdraw from the bankroll.
97
+
98
+ Raises
99
+ ------
100
+ RuinError
101
+ If the withdrawal would exceed the maximum allowed drawdown.
102
+ """
103
+ self._bank -= amt
104
+ if self.max_draw_down and amt > self.max_draw_down * self.total_funds:
105
+ raise RuinError("You lost too much money buddy, slow down.")
106
+
107
+ self.update_history()
108
+
109
+ def bet(self, amount):
110
+ """
111
+ Place a bet with the specified amount.
112
+
113
+ Parameters
114
+ ----------
115
+ amount : float
116
+ The amount to bet.
117
+
118
+ Raises
119
+ ------
120
+ ValueError
121
+ If the bet amount exceeds the bettable funds.
122
+ """
123
+ if amount > self.bettable_funds:
124
+ raise ValueError("Bet amount exceeds bettable funds")
125
+ self._bank -= amount
126
+ self.update_history()
127
+
128
+ def add_funds(self, amount):
129
+ """
130
+ Add funds to the bankroll after a winning bet.
131
+
132
+ Parameters
133
+ ----------
134
+ amount : float
135
+ The amount to add to the bankroll.
136
+ """
137
+ self._bank += amount
138
+ self.update_history()
139
+
140
+ def remove_funds(self, amount):
141
+ """
142
+ Remove funds from the bankroll after a losing bet.
143
+
144
+ Parameters
145
+ ----------
146
+ amount : float
147
+ The amount to remove from the bankroll.
148
+
149
+ Raises
150
+ ------
151
+ RuinError
152
+ If the removal would exceed the maximum allowed drawdown.
153
+ """
154
+ if self.max_draw_down and amount > self.max_draw_down * self.total_funds:
155
+ raise RuinError("You lost too much money buddy, slow down.")
156
+ self._bank -= amount
157
+ self.update_history()
158
+
159
+ def plot_history(self, fname=None):
160
+ """
161
+ Plot the history of the bankroll over time.
162
+
163
+ Creates a line plot showing the bankroll value after each transaction.
164
+
165
+ Parameters
166
+ ----------
167
+ fname : str, optional
168
+ If provided, saves the plot to the specified filename instead of displaying it.
169
+ """
170
+ plt.figure()
171
+ plt.plot(list(range(len(self.history))), self.history, fmt="bo-")
172
+ if fname:
173
+ plt.savefig(fname)
174
+ else:
175
+ plt.show()
@@ -0,0 +1,41 @@
1
+ """
2
+ Binary betting strategies for the keeks package.
3
+
4
+ This module provides various strategies for binary betting scenarios, including:
5
+ - Kelly Criterion: Optimal bet sizing based on probability and odds
6
+ - Fractional Kelly: A more conservative version of Kelly using a fraction
7
+ - Drawdown-Adjusted Kelly: Kelly variant that accounts for drawdown tolerance
8
+ - Optimal f: Ralph Vince's method for maximizing geometric growth rate
9
+ - Fixed Fraction: Simple strategy that bets a constant percentage
10
+ - CPPI: Constant Proportion Portfolio Insurance for preserving capital
11
+ - Dynamic Bankroll Management: Adaptive allocation based on recent performance
12
+ - Naive Strategy: Simple strategy that bets full amount when expected value is positive
13
+
14
+ All strategies implement the BaseStrategy interface.
15
+ """
16
+
17
+ from keeks.binary_strategies.kelly import (
18
+ DrawdownAdjustedKelly,
19
+ FractionalKellyCriterion,
20
+ KellyCriterion,
21
+ )
22
+ from keeks.binary_strategies.simple import (
23
+ CPPIStrategy,
24
+ DynamicBankrollManagement,
25
+ FixedFractionStrategy,
26
+ NaiveStrategy,
27
+ OptimalF,
28
+ )
29
+
30
+ __author__ = "willmcginnis"
31
+
32
+ __all__ = [
33
+ "KellyCriterion",
34
+ "FractionalKellyCriterion",
35
+ "DrawdownAdjustedKelly",
36
+ "OptimalF",
37
+ "NaiveStrategy",
38
+ "FixedFractionStrategy",
39
+ "CPPIStrategy",
40
+ "DynamicBankrollManagement",
41
+ ]
@@ -0,0 +1,84 @@
1
+ import abc
2
+
3
+ __author__ = "willmcginnis"
4
+
5
+
6
+ class BaseStrategy(abc.ABC):
7
+ """
8
+ Abstract base class for all binary betting strategies.
9
+
10
+ This class defines the interface that all binary betting strategies must implement.
11
+ Concrete strategy implementations should inherit from this class and implement
12
+ the evaluate method.
13
+ """
14
+
15
+ def __init__(self, payoff: float, loss: float, transaction_cost: float = 0):
16
+ """
17
+ Initialize the strategy.
18
+
19
+ Parameters
20
+ ----------
21
+ payoff : float
22
+ The payoff multiplier for winning.
23
+ loss : float
24
+ The loss multiplier for losing.
25
+ transaction_cost : float, optional
26
+ The transaction cost as a fraction of the bet, by default 0.
27
+
28
+ Raises
29
+ ------
30
+ ValueError
31
+ If payoff is not positive, loss is negative, or loss + transaction_cost is not positive.
32
+ """
33
+ if payoff <= 0:
34
+ raise ValueError("Payoff must be greater than 0")
35
+ if loss < 0:
36
+ raise ValueError("Loss must be non-negative")
37
+ if transaction_cost < 0:
38
+ raise ValueError("Transaction cost must be non-negative")
39
+ if loss + transaction_cost <= 0:
40
+ raise ValueError(
41
+ "Total cost (loss + transaction_cost) must be greater than 0"
42
+ )
43
+
44
+ self.payoff = payoff
45
+ self.loss = loss
46
+ self.transaction_cost = transaction_cost
47
+
48
+ def get_max_safe_bet(self, current_bankroll: float) -> float:
49
+ """
50
+ Calculate the maximum safe bet size based on current bankroll.
51
+
52
+ Parameters
53
+ ----------
54
+ current_bankroll : float
55
+ The current bankroll to use for calculations.
56
+
57
+ Returns
58
+ -------
59
+ float
60
+ The maximum safe bet size as a proportion of bankroll.
61
+ """
62
+ # Calculate maximum bet that won't result in negative bankroll
63
+ # after accounting for loss multiplier and transaction costs
64
+ max_bet = current_bankroll / (self.loss + self.transaction_cost)
65
+ return min(1.0, max_bet / current_bankroll)
66
+
67
+ @abc.abstractmethod
68
+ def evaluate(self, probability: float, current_bankroll: float) -> float:
69
+ """
70
+ Evaluate the strategy for a given probability.
71
+
72
+ Parameters
73
+ ----------
74
+ probability : float
75
+ The probability of winning.
76
+ current_bankroll : float
77
+ The current bankroll to use for calculations.
78
+
79
+ Returns
80
+ -------
81
+ float
82
+ The proportion of the bankroll to bet.
83
+ """
84
+ pass
@@ -0,0 +1,227 @@
1
+ from keeks.binary_strategies.base import BaseStrategy
2
+
3
+ __author__ = "willmcginnis"
4
+
5
+
6
+ class KellyCriterion(BaseStrategy):
7
+ """
8
+ Implementation of the Kelly Criterion for binary betting.
9
+
10
+ The Kelly Criterion is a mathematical formula that determines the optimal
11
+ size of a series of bets to maximize long-term growth rate.
12
+
13
+ Parameters
14
+ ----------
15
+ payoff : float
16
+ The amount won per unit bet on a successful outcome.
17
+ loss : float
18
+ The amount lost per unit bet on an unsuccessful outcome.
19
+ transaction_cost : float
20
+ The fixed cost per transaction, regardless of outcome.
21
+ min_probability : float, default=0.5
22
+ The minimum probability required to place a bet.
23
+ """
24
+
25
+ def __init__(self, payoff, loss, transaction_cost, min_probability=0.5):
26
+ """
27
+ Initialize the Kelly Criterion strategy.
28
+
29
+ Parameters
30
+ ----------
31
+ payoff : float
32
+ The amount won per unit bet on a successful outcome.
33
+ loss : float
34
+ The amount lost per unit bet on an unsuccessful outcome.
35
+ transaction_cost : float
36
+ The fixed cost per transaction, regardless of outcome.
37
+ min_probability : float, default=0.5
38
+ The minimum probability required to place a bet.
39
+ """
40
+ if not 0 <= min_probability <= 1:
41
+ raise ValueError("Minimum probability must be between 0 and 1")
42
+
43
+ super().__init__(payoff, loss, transaction_cost)
44
+ self.min_probability = min_probability
45
+
46
+ def evaluate(self, probability, current_bankroll):
47
+ """
48
+ Calculate the optimal Kelly bet size.
49
+
50
+ The Kelly Criterion formula is:
51
+ f* = (bp - q) / b
52
+
53
+ where:
54
+ f* = fraction of current bankroll to bet
55
+ b = net odds received on the bet (payoff/loss)
56
+ p = probability of winning
57
+ q = probability of losing (1 - p)
58
+
59
+ Parameters
60
+ ----------
61
+ probability : float
62
+ The probability of a successful outcome, typically between 0 and 1.
63
+ current_bankroll : float
64
+ The current bankroll amount.
65
+
66
+ Returns
67
+ -------
68
+ float
69
+ The optimal proportion of the bankroll to bet.
70
+ """
71
+ if probability < self.min_probability:
72
+ return 0.0
73
+
74
+ # Calculate net odds
75
+ self.payoff / self.loss
76
+
77
+ # Calculate probability of losing
78
+ q = 1 - probability
79
+
80
+ # Calculate Kelly fraction with transaction costs incorporated
81
+ # Adjust payoff and loss for transaction costs
82
+ adjusted_payoff = self.payoff - self.transaction_cost
83
+ adjusted_loss = self.loss + self.transaction_cost
84
+
85
+ # Recalculate net odds with transaction costs
86
+ if adjusted_payoff <= 0 or adjusted_loss <= 0:
87
+ return 0.0 # If transaction costs make the bet unprofitable
88
+
89
+ adjusted_b = adjusted_payoff / adjusted_loss
90
+
91
+ # Calculate Kelly fraction with adjusted odds
92
+ kelly_fraction = (adjusted_b * probability - q) / adjusted_b
93
+
94
+ # Ensure we never bet more than would result in negative bankroll
95
+ return min(max(0, kelly_fraction), self.get_max_safe_bet(current_bankroll))
96
+
97
+
98
+ class FractionalKellyCriterion(BaseStrategy):
99
+ """
100
+ Implementation of the Fractional Kelly Criterion for binary betting.
101
+
102
+ The Fractional Kelly Criterion applies a fraction to the full Kelly bet size,
103
+ which can reduce volatility at the expense of long-term growth rate.
104
+
105
+ Parameters
106
+ ----------
107
+ payoff : float
108
+ The amount won per unit bet on a successful outcome.
109
+ loss : float
110
+ The amount lost per unit bet on an unsuccessful outcome.
111
+ transaction_cost : float
112
+ The fixed cost per transaction, regardless of outcome.
113
+ fraction : float
114
+ The fraction of the full Kelly bet to use (typically between 0 and 1).
115
+ """
116
+
117
+ def __init__(self, payoff, loss, transaction_cost, fraction):
118
+ if not 0 <= fraction <= 1:
119
+ raise ValueError("Fraction must be between 0 and 1")
120
+
121
+ super().__init__(payoff, loss, transaction_cost)
122
+ self.fraction = fraction
123
+
124
+ def evaluate(self, probability, current_bankroll):
125
+ """
126
+ Calculate the fractional Kelly bet size.
127
+
128
+ Parameters
129
+ ----------
130
+ probability : float
131
+ The probability of a successful outcome, typically between 0 and 1.
132
+ current_bankroll : float
133
+ The current bankroll amount.
134
+
135
+ Returns
136
+ -------
137
+ float
138
+ The optimal proportion of the bankroll to bet, multiplied by the fraction parameter.
139
+ """
140
+ kelly = KellyCriterion(self.payoff, self.loss, self.transaction_cost)
141
+ return self.fraction * kelly.evaluate(probability, current_bankroll)
142
+
143
+
144
+ class DrawdownAdjustedKelly(BaseStrategy):
145
+ """
146
+ Implementation of the Drawdown-Adjusted Kelly Criterion for binary betting.
147
+
148
+ This strategy adjusts the Kelly bet size based on a maximum acceptable drawdown.
149
+ It provides a more conservative approach by reducing the bet size to minimize
150
+ the risk of large drawdowns.
151
+
152
+ Parameters
153
+ ----------
154
+ payoff : float
155
+ The amount won per unit bet on a successful outcome.
156
+ loss : float
157
+ The amount lost per unit bet on an unsuccessful outcome.
158
+ transaction_cost : float
159
+ The fixed cost per transaction, regardless of outcome.
160
+ max_acceptable_drawdown : float
161
+ The maximum acceptable drawdown as a fraction of the bankroll (0 to 1).
162
+ """
163
+
164
+ def __init__(self, payoff, loss, transaction_cost, max_acceptable_drawdown=0.2):
165
+ """
166
+ Initialize the DrawdownAdjustedKelly strategy.
167
+
168
+ Parameters
169
+ ----------
170
+ payoff : float
171
+ The amount won per unit bet on a successful outcome.
172
+ loss : float
173
+ The amount lost per unit bet on an unsuccessful outcome.
174
+ transaction_cost : float
175
+ The fixed cost per transaction, regardless of outcome.
176
+ max_acceptable_drawdown : float, default=0.2
177
+ The maximum acceptable drawdown as a fraction of the bankroll.
178
+
179
+ Raises
180
+ ------
181
+ ValueError
182
+ If max_acceptable_drawdown is not between 0 and 1 (exclusive).
183
+ """
184
+ super().__init__(payoff, loss, transaction_cost)
185
+
186
+ if not 0 < max_acceptable_drawdown < 1:
187
+ raise ValueError(
188
+ "Maximum acceptable drawdown must be between 0 and 1 (exclusive)"
189
+ )
190
+
191
+ self.max_acceptable_drawdown = max_acceptable_drawdown
192
+
193
+ def evaluate(self, probability, current_bankroll):
194
+ """
195
+ Calculate the drawdown-adjusted Kelly bet size.
196
+
197
+ The adjustment is an approximation based on the relationship between
198
+ bet size and expected drawdown in repeated betting scenarios.
199
+
200
+ Parameters
201
+ ----------
202
+ probability : float
203
+ The probability of a successful outcome, typically between 0 and 1.
204
+ current_bankroll : float
205
+ The current bankroll amount.
206
+
207
+ Returns
208
+ -------
209
+ float
210
+ The drawdown-adjusted proportion of the bankroll to bet.
211
+ """
212
+ # Calculate the standard Kelly bet size
213
+ kelly = KellyCriterion(self.payoff, self.loss, self.transaction_cost)
214
+ full_kelly = kelly.evaluate(probability, current_bankroll)
215
+
216
+ # Adjust the Kelly fraction based on maximum acceptable drawdown
217
+ # This is a simplified approximation of the relationship
218
+ # Various research suggests specific formulas, but a common
219
+ # conservative approach is to scale Kelly by max drawdown / 0.5
220
+ # (since full Kelly has an expected drawdown of around 50%)
221
+ drawdown_factor = min(1.0, self.max_acceptable_drawdown / 0.5)
222
+
223
+ # Apply the drawdown adjustment
224
+ adjusted_kelly = drawdown_factor * full_kelly
225
+
226
+ # Ensure we never bet more than would result in negative bankroll
227
+ return min(adjusted_kelly, self.get_max_safe_bet(current_bankroll))