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 +14 -0
- keeks/bankroll.py +175 -0
- keeks/binary_strategies/__init__.py +41 -0
- keeks/binary_strategies/base.py +84 -0
- keeks/binary_strategies/kelly.py +227 -0
- keeks/binary_strategies/simple.py +548 -0
- keeks/simulators/__init__.py +11 -0
- keeks/simulators/random_binary.py +66 -0
- keeks/simulators/random_uncertain_binary.py +82 -0
- keeks/simulators/repeated_binary.py +68 -0
- keeks/utils.py +9 -0
- keeks-0.2.0.dist-info/METADATA +237 -0
- keeks-0.2.0.dist-info/RECORD +15 -0
- keeks-0.2.0.dist-info/WHEEL +4 -0
- keeks-0.2.0.dist-info/licenses/LICENSE.md +8 -0
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))
|