Source code for keeks.binary_strategies.kelly

from keeks.binary_strategies.base import BaseStrategy

__author__ = "willmcginnis"


[docs]class KellyCriterion(BaseStrategy): """ Implementation of the Kelly Criterion for binary betting. The Kelly Criterion is a mathematical formula that determines the optimal size of a series of bets to maximize long-term growth rate. Parameters ---------- payoff : float The amount won per unit bet on a successful outcome. loss : float The amount lost per unit bet on an unsuccessful outcome. transaction_cost : float The fixed cost per transaction, regardless of outcome. min_probability : float, default=0.5 The minimum probability required to place a bet. """ def __init__(self, payoff, loss, transaction_cost, min_probability=0.5): """ Initialize the Kelly Criterion strategy. Parameters ---------- payoff : float The amount won per unit bet on a successful outcome. loss : float The amount lost per unit bet on an unsuccessful outcome. transaction_cost : float The fixed cost per transaction, regardless of outcome. min_probability : float, default=0.5 The minimum probability required to place a bet. """ if not 0 <= min_probability <= 1: raise ValueError("Minimum probability must be between 0 and 1") super().__init__(payoff, loss, transaction_cost) self.min_probability = min_probability
[docs] def evaluate(self, probability, current_bankroll): """ Calculate the optimal Kelly bet size. The Kelly Criterion formula is: f* = (bp - q) / b where: f* = fraction of current bankroll to bet b = net odds received on the bet (payoff/loss) p = probability of winning q = probability of losing (1 - p) Parameters ---------- probability : float The probability of a successful outcome, typically between 0 and 1. current_bankroll : float The current bankroll amount. Returns ------- float The optimal proportion of the bankroll to bet. """ if probability < self.min_probability: return 0.0 # Calculate net odds self.payoff / self.loss # Calculate probability of losing q = 1 - probability # Calculate Kelly fraction with transaction costs incorporated # Adjust payoff and loss for transaction costs adjusted_payoff = self.payoff - self.transaction_cost adjusted_loss = self.loss + self.transaction_cost # Recalculate net odds with transaction costs if adjusted_payoff <= 0 or adjusted_loss <= 0: return 0.0 # If transaction costs make the bet unprofitable adjusted_b = adjusted_payoff / adjusted_loss # Calculate Kelly fraction with adjusted odds kelly_fraction = (adjusted_b * probability - q) / adjusted_b # Ensure we never bet more than would result in negative bankroll return min(max(0, kelly_fraction), self.get_max_safe_bet(current_bankroll))
[docs]class FractionalKellyCriterion(BaseStrategy): """ Implementation of the Fractional Kelly Criterion for binary betting. The Fractional Kelly Criterion applies a fraction to the full Kelly bet size, which can reduce volatility at the expense of long-term growth rate. Parameters ---------- payoff : float The amount won per unit bet on a successful outcome. loss : float The amount lost per unit bet on an unsuccessful outcome. transaction_cost : float The fixed cost per transaction, regardless of outcome. fraction : float The fraction of the full Kelly bet to use (typically between 0 and 1). """ def __init__(self, payoff, loss, transaction_cost, fraction): if not 0 <= fraction <= 1: raise ValueError("Fraction must be between 0 and 1") super().__init__(payoff, loss, transaction_cost) self.fraction = fraction
[docs] def evaluate(self, probability, current_bankroll): """ Calculate the fractional Kelly bet size. Parameters ---------- probability : float The probability of a successful outcome, typically between 0 and 1. current_bankroll : float The current bankroll amount. Returns ------- float The optimal proportion of the bankroll to bet, multiplied by the fraction parameter. """ kelly = KellyCriterion(self.payoff, self.loss, self.transaction_cost) return self.fraction * kelly.evaluate(probability, current_bankroll)
[docs]class DrawdownAdjustedKelly(BaseStrategy): """ Implementation of the Drawdown-Adjusted Kelly Criterion for binary betting. This strategy adjusts the Kelly bet size based on a maximum acceptable drawdown. It provides a more conservative approach by reducing the bet size to minimize the risk of large drawdowns. Parameters ---------- payoff : float The amount won per unit bet on a successful outcome. loss : float The amount lost per unit bet on an unsuccessful outcome. transaction_cost : float The fixed cost per transaction, regardless of outcome. max_acceptable_drawdown : float The maximum acceptable drawdown as a fraction of the bankroll (0 to 1). """ def __init__(self, payoff, loss, transaction_cost, max_acceptable_drawdown=0.2): """ Initialize the DrawdownAdjustedKelly strategy. Parameters ---------- payoff : float The amount won per unit bet on a successful outcome. loss : float The amount lost per unit bet on an unsuccessful outcome. transaction_cost : float The fixed cost per transaction, regardless of outcome. max_acceptable_drawdown : float, default=0.2 The maximum acceptable drawdown as a fraction of the bankroll. Raises ------ ValueError If max_acceptable_drawdown is not between 0 and 1 (exclusive). """ super().__init__(payoff, loss, transaction_cost) if not 0 < max_acceptable_drawdown < 1: raise ValueError( "Maximum acceptable drawdown must be between 0 and 1 (exclusive)" ) self.max_acceptable_drawdown = max_acceptable_drawdown
[docs] def evaluate(self, probability, current_bankroll): """ Calculate the drawdown-adjusted Kelly bet size. The adjustment is an approximation based on the relationship between bet size and expected drawdown in repeated betting scenarios. Parameters ---------- probability : float The probability of a successful outcome, typically between 0 and 1. current_bankroll : float The current bankroll amount. Returns ------- float The drawdown-adjusted proportion of the bankroll to bet. """ # Calculate the standard Kelly bet size kelly = KellyCriterion(self.payoff, self.loss, self.transaction_cost) full_kelly = kelly.evaluate(probability, current_bankroll) # Adjust the Kelly fraction based on maximum acceptable drawdown # This is a simplified approximation of the relationship # Various research suggests specific formulas, but a common # conservative approach is to scale Kelly by max drawdown / 0.5 # (since full Kelly has an expected drawdown of around 50%) drawdown_factor = min(1.0, self.max_acceptable_drawdown / 0.5) # Apply the drawdown adjustment adjusted_kelly = drawdown_factor * full_kelly # Ensure we never bet more than would result in negative bankroll return min(adjusted_kelly, self.get_max_safe_bet(current_bankroll))