Binary Strategies

Binary strategies are strategies that reason about singular binary bets. These bets have some payoff if they win, some loss if they lose, and a transaction cost regardless. Bets are not continuous, they either win or lose.

Base Strategy

class keeks.binary_strategies.base.BaseStrategy(payoff: float, loss: float, transaction_cost_rate: float = 0)[source]

Bases: ParameterMixin, ABC

Abstract base class for all binary betting strategies.

This class defines the interface that all binary betting strategies must implement. Concrete strategy implementations should inherit from this class and implement the evaluate method.

Beyond evaluate, the simulators in keeks.simulators resolve two optional hooks getattr-style once per run and fire them around each settled bet. This base class implements neither, so a strategy may omit them freely: the simulator skips the hook and the strategy stays stateless. A stateful strategy that needs the feedback but omits the hook simply sizes from stale state - there is no error, which is why the contract is documented here.

  • update_bankroll(current_bankroll) - fired at the top of every trial, before evaluate, with the bankroll’s current total funds, so an adaptive strategy can size from the bankroll as it stands (keeks.binary_strategies.CPPIStrategy ratchets its floor from it). Not fired once the bankroll is depleted - the simulation has already stopped.

  • record_settlement(won, realized_returns) - fired once per settled bet, after the bankroll transfer: won holds the bet’s realized outcome (one entry per settled option - a single True or False for the binary simulators) and realized_returns the realized simple return per option on the bankroll, positive or negative. Not fired for trials the strategy sits out (a zero stake), and not fired for a settlement a bankroll safeguard refused - a refused settlement leaves the bankroll unchanged and stops the simulation, so there is no result to report (keeks.binary_strategies.DynamicBankrollManagement tracks streaks and volatility through this hook).

A hook should not raise: exceptions other than the simulator’s own RuinError settlement handling propagate to the caller.

calculate_max_entry_price(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, tolerance: float = 0.01, max_search_fraction: float = 0.5) → float[source]

Calculate maximum price willing to pay for a one-time gamble.

This method is only implemented for strategies derived from utility theory (e.g., KellyCriterion, MertonShare). For heuristic strategies without underlying utility functions, this method raises NotImplementedError.

Parameters:
  • outcomes (array-like) – The possible payoffs from the gamble

  • probabilities (array-like) – The probability of each outcome (must sum to ≤ 1)

  • current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.

  • tolerance (float, default=0.01) – Convergence tolerance for binary search. Must be finite and greater than 0.

  • max_search_fraction (float, default=0.5) – Maximum fraction of wealth to consider as upper bound. Must be finite and non-negative; values above 1.0 are allowed.

Returns:

Maximum price willing to pay for the gamble

Return type:

float

Raises:
  • NotImplementedError – If the strategy does not have a utility-theoretic basis for calculating indifference prices.

  • ValueError – Raised by every shipped implementation if the gamble arrays are malformed or if any scalar control falls outside the ranges documented above. Overrides should validate with keeks.utils._validate_entry_price_scalars before doing work or mutating internal state.

Warns:

RuntimeWarning – Emitted by the utility-based implementations, which delegate to keeks.utils.find_indifference_price, when the gamble is still worth buying at current_wealth * max_search_fraction. The returned price is then that bound rather than a solved indifference price. See find_indifference_price for details.

Notes

This method answers: “What’s the maximum I’d pay to participate in this one-time gamble?” It’s fundamentally different from evaluate(), which answers: “How much should I bet in a repeated betting scenario?”

Only strategies with underlying utility functions can meaningfully answer the entry price question. Heuristic strategies (e.g., FixedFraction, CPPI) don’t have utility functions to derive indifference prices from.

abstract evaluate(probability: float, current_bankroll: float) → float[source]

Evaluate the strategy for a given probability.

Parameters:
  • probability (float) – The probability of winning.

  • current_bankroll (float) – The current bankroll to use for calculations.

Returns:

The proportion of the bankroll to bet - 0.0 for a nonpositive bankroll, where there is nothing left to stake.

Return type:

float

Raises:

ValueError – If probability is not finite and between 0 and 1 inclusive, or if current_bankroll is not finite.

get_max_safe_bet(current_bankroll: float) → float[source]

Calculate the maximum safe bet size based on current bankroll.

Parameters:

current_bankroll (float) – The current bankroll to use for calculations.

Returns:

The maximum safe bet size as a proportion of bankroll. Zero when there is nothing left to stake (current_bankroll <= 0).

Return type:

float

Raises:

ValueError – If current_bankroll is not finite.

Notes

The maximum stake that cannot drive the bankroll negative is current_bankroll / (loss + transaction_cost_rate); expressed as a proportion of the bankroll the bankroll term cancels, so for a positive bankroll this is exactly min(1.0, 1 / (loss + transaction_cost_rate)). A non-positive bankroll has no safe stake at all, so the answer there is 0.0.

Naive Strategy

class keeks.binary_strategies.simple.NaiveStrategy(payoff: float, loss: float, transaction_cost_rate: float)[source]

Bases: BaseStrategy

A simple betting strategy that bets based on expected value.

This strategy calculates the expected value of a bet and bets a fixed fraction of the bankroll if the expected value is positive.

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_rate (float) – The transaction cost as a fraction of each unit staked (per-unit, not a fixed per-transaction amount).

calculate_max_entry_price(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, tolerance: float = 0.01, max_search_fraction: float = 0.5) → float[source]

Calculate maximum price willing to pay for a one-time gamble.

Naive strategy is risk-neutral and simply pays the expected value.

Parameters:
  • outcomes (array-like) – The possible payoffs from the gamble

  • probabilities (array-like) – The probability of each outcome (must sum to ≤ 1)

  • current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.

  • tolerance (float, default=0.01) – Convergence tolerance (unused, kept for API consistency). Must still be finite and greater than 0.

  • max_search_fraction (float, default=0.5) – Maximum fraction of wealth to consider as upper bound. Must be finite and non-negative; values above 1.0 are allowed.

Returns:

Maximum price willing to pay (the expected value, bounded above by current_wealth * max_search_fraction)

Return type:

float

Raises:

ValueError – If the gamble arrays are malformed, or if any scalar control falls outside the ranges documented above.

Notes

Naive strategy is risk-neutral: willing to pay exactly the expected value. This is the classic “expected value maximizer” that doesn’t account for risk. The expected value can be unbounded (e.g. the St. Petersburg paradox), so the result is capped the same way every other strategy caps it. Pass max_search_fraction=1.0 to allow paying the entire bankroll.

evaluate(probability: float, current_bankroll: float) → float[source]

Calculate the bet size based on expected value.

The expected value is calculated as: EV = (probability * payoff) - ((1 - probability) * loss) - transaction_cost_rate

If EV is positive, bet a fixed fraction of the bankroll. If EV is negative, do not bet.

Parameters:
  • probability (float) – The probability of a successful outcome, typically between 0 and 1.

  • current_bankroll (float) – The current bankroll amount.

Returns:

The proportion of the bankroll to bet.

Return type:

float

Fixed Fraction Strategy

class keeks.binary_strategies.simple.FixedFractionStrategy(fraction: float, payoff: float, loss: float, transaction_cost_rate: float = 0, min_probability: float = 0.5)[source]

Bases: BaseStrategy

A simple strategy that bets a fixed percentage of the bankroll.

This strategy ignores probabilities and odds completely, and simply wagers a fixed fraction of the bankroll. It can be used as a baseline for comparison or as a simple approach for risk management.

Parameters:
  • fraction (float) – The fixed fraction of the bankroll to bet (between 0 and 1).

  • 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_rate (float, default=0) – The transaction cost as a fraction of each unit staked (per-unit, not a fixed per-transaction amount).

  • min_probability (float, default=0.5) – The minimum probability required to place a bet.

calculate_max_entry_price(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, tolerance: float = 0.01, max_search_fraction: float = 0.5) → float[source]

Calculate maximum price willing to pay for a one-time gamble.

Fixed fraction strategy simply pays a fixed fraction of current wealth.

Parameters:
  • outcomes (array-like) – The possible payoffs from the gamble (unused)

  • probabilities (array-like) – The probability of each outcome (unused)

  • current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.

  • tolerance (float, default=0.01) – Convergence tolerance (unused, kept for API consistency). Must still be finite and greater than 0.

  • max_search_fraction (float, default=0.5) – Maximum fraction of wealth to consider as upper bound. Must be finite and non-negative; values above 1.0 are allowed.

Returns:

Maximum price willing to pay (fixed fraction of wealth)

Return type:

float

Raises:

ValueError – If the gamble arrays are malformed, or if any scalar control falls outside the ranges documented above.

Notes

Fixed fraction strategy doesn’t analyze the gamble - it simply commits a fixed fraction of wealth regardless of the opportunity. This is a mechanical rule-based approach, not optimization-based.

evaluate(probability: float, current_bankroll: float) → float[source]

Return the fixed fraction if the probability meets the minimum threshold.

Parameters:
  • probability (float) – The probability of a successful outcome, typically between 0 and 1.

  • current_bankroll (float) – The current bankroll amount.

Returns:

The fixed fraction if probability >= min_probability, otherwise 0.

Return type:

float

CPPI Strategy

class keeks.binary_strategies.simple.CPPIStrategy(floor_fraction: float, multiplier: float, initial_bankroll: float, payoff: float, loss: float, transaction_cost_rate: float = 0, min_probability: float = 0.5)[source]

Bases: BaseStrategy

Implementation of Constant Proportion Portfolio Insurance (CPPI) for binary betting.

This strategy maintains a dynamic floor below which the bankroll should not fall, and invests a multiple of the cushion (amount above floor) in each bet.

Parameters:
  • floor_fraction (float) – The fraction of initial bankroll to maintain as a floor.

  • multiplier (float) – The multiplier to apply to the cushion for determining exposure.

  • initial_bankroll (float) – The initial bankroll amount.

  • 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_rate (float) – The transaction cost as a fraction of each unit staked (per-unit, not a fixed per-transaction amount).

  • min_probability (float, default=0.5) – The minimum probability required to place a bet.

calculate_max_entry_price(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, tolerance: float = 0.01, max_search_fraction: float = 0.5) → float[source]

Calculate maximum price willing to pay for a one-time gamble.

CPPI protects a floor value, so entry price is based on the cushion above the floor, scaled by the multiplier.

Parameters:
  • outcomes (array-like) – The possible payoffs from the gamble (unused in basic calculation)

  • probabilities (array-like) – The probability of each outcome (unused in basic calculation)

  • current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.

  • tolerance (float, default=0.01) – Convergence tolerance (unused, kept for API consistency). Must still be finite and greater than 0.

  • max_search_fraction (float, default=0.5) – Maximum fraction of wealth to consider as upper bound. Must be finite and non-negative; values above 1.0 are allowed.

Returns:

Maximum price willing to pay (based on cushion above floor)

Return type:

float

Raises:

ValueError – If the gamble arrays are malformed, or if any scalar control falls outside the ranges documented above. Validation happens before any internal bankroll or floor state is updated.

Notes

CPPI maintains a floor value and bets a multiple of the cushion. For entry price, we apply the same logic: pay multiplier × cushion, but never more than the cushion itself (to maintain floor).

evaluate(probability: float, current_bankroll: float) → float[source]

Calculate the CPPI bet size based on the cushion above the floor.

Parameters:
  • probability (float) – The probability of a successful outcome, typically between 0 and 1.

  • current_bankroll (float) – The current bankroll amount.

Returns:

The proportion of the current bankroll to bet, or 0 if below the minimum probability threshold.

Return type:

float

update_bankroll(new_bankroll: float) → None[source]

Update the current bankroll value and adjust floor based on peak value.

Parameters:

new_bankroll (float) – The current value of the bankroll.

Dynamic Bankroll Management

class keeks.binary_strategies.simple.DynamicBankrollManagement(base_fraction: float, payoff: float, loss: float, transaction_cost_rate: float, window_size: int = 10, max_fraction: float = 0.2, min_fraction: float = 0.05, min_probability: float = 0.5)[source]

Bases: BaseStrategy

A dynamic bankroll management strategy that adjusts bet sizes based on performance.

This strategy adjusts the base bet fraction based on: 1. Recent performance (win/loss streak) 2. Volatility of returns 3. Current drawdown level 4. Probability of success

Parameters:
  • base_fraction (float) – The base fraction of bankroll to bet.

  • 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_rate (float) – The transaction cost as a fraction of each unit staked (per-unit, not a fixed per-transaction amount).

  • window_size (int, default=10) – The number of recent results to consider for adjustments.

  • max_fraction (float, default=0.2) – The maximum fraction of bankroll that can be bet.

  • min_fraction (float, default=0.05) – The minimum fraction of bankroll to bet.

  • min_probability (float, default=0.5) – The minimum probability required to place a bet. Below this the strategy returns 0.0 (no bet).

calculate_max_entry_price(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, tolerance: float = 0.01, max_search_fraction: float = 0.5) → float[source]

Calculate maximum price willing to pay for a one-time gamble.

Dynamic strategy adjusts based on history, but for a one-time decision with no history, we use the base fraction.

Parameters:
  • outcomes (array-like) – The possible payoffs from the gamble (unused)

  • probabilities (array-like) – The probability of each outcome (unused)

  • current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.

  • tolerance (float, default=0.01) – Convergence tolerance (unused, kept for API consistency). Must still be finite and greater than 0.

  • max_search_fraction (float, default=0.5) – Maximum fraction of wealth to consider as upper bound. Must be finite and non-negative; values above 1.0 are allowed.

Returns:

Maximum price willing to pay (base fraction of wealth)

Return type:

float

Raises:

ValueError – If the gamble arrays are malformed, or if any scalar control falls outside the ranges documented above.

Notes

Dynamic strategy normally adjusts based on recent performance history. For a one-time decision with no history, we fall back to the base_fraction. This represents a neutral starting point before dynamic adjustments.

evaluate(probability: float, current_bankroll: float) → float[source]

Calculate the bet size based on all adjustment factors.

Parameters:
  • probability (float) – The probability of a successful outcome.

  • current_bankroll (float) – The current bankroll amount.

Returns:

The proportion of the current bankroll to bet.

Return type:

float

get_drawdown_factor() → float[source]

Calculate the adjustment factor based on current drawdown.

get_probability_factor(probability: float) → float[source]

Calculate the adjustment factor based on probability.

get_streak_factor() → float[source]

Calculate the adjustment factor based on recent performance.

get_volatility_factor() → float[source]

Calculate the adjustment factor based on return volatility.

record_settlement(won: Sequence[bool], realized_returns: Sequence[float] | None = None) → None[source]

Record the result of a settled bet.

Parameters:
  • won (sequence of bool) – One entry per settled option: True for a win, False for a loss.

  • realized_returns (sequence of float, optional) – The realized simple return per option on the bankroll. If not provided, calculated from won and the strategy’s odds.

Kelly Criterion

class keeks.binary_strategies.kelly.KellyCriterion(payoff: float, loss: float, transaction_cost_rate: float, min_probability: float | None = None)[source]

Bases: 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.

By default the gate is edge-aware: a bet the Kelly formula itself sizes positively is always sized, and a bet with no positive edge returns 0.0. An optional min_probability longshot gate can additionally refuse low-probability bets - loudly, when the refused bet is one the formula would have sized.

Parameters:
  • payoff (float) – The amount won per unit bet on a successful outcome: the net win, excluding the stake’s return - decimal odds minus one, so even money is 1.0. Unlike here, BinaryBetsModel and the multi-outcome layer read payoff as the decimal odds themselves.

  • loss (float) – The amount lost per unit bet on an unsuccessful outcome.

  • transaction_cost_rate (float) – The transaction cost as a fraction of each unit staked (per-unit, not a fixed per-transaction amount).

  • min_probability (float or None, optional) – An optional longshot gate: when set, bets with a win probability below it return 0.0 even when the Kelly fraction is positive, and a UserWarning names the suppressed fraction so the refusal is never silent. The default (None) sizes on edge alone: with payoff=10, loss=1 and probability 0.3, the true Kelly fraction of about 0.23 is placed instead of being zeroed.

Examples

>>> strategy = KellyCriterion(payoff=10, loss=1, transaction_cost_rate=0)
>>> # Edge-aware by default: the formula sizes this longshot at 0.23.
>>> round(strategy.evaluate(0.3, 1000.0), 4)
0.23
>>> # A bet with no positive edge refuses itself.
>>> round(strategy.evaluate(0.05, 1000.0), 4)
0
calculate_max_entry_price(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, tolerance: float = 0.01, max_search_fraction: float = 0.5) → float[source]

Calculate maximum price willing to pay for a one-time gamble.

Kelly Criterion is derived from log utility maximization (CRRA with γ=1.0), so this uses log utility to find the indifference price.

Parameters:
  • outcomes (array-like) – The possible payoffs from the gamble

  • probabilities (array-like) – The probability of each outcome (must sum to ≤ 1)

  • current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.

  • tolerance (float, default=0.01) – Convergence tolerance for binary search. Must be finite and greater than 0.

  • max_search_fraction (float, default=0.5) – Maximum fraction of wealth to consider as upper bound. Must be finite and non-negative; values above 1.0 are allowed.

Returns:

Maximum price willing to pay for the gamble

Return type:

float

Raises:

ValueError – If the gamble arrays are malformed, or if any scalar control falls outside the ranges documented above.

Notes

Kelly Criterion maximizes E[log(wealth)], which corresponds to CRRA utility with risk aversion γ=1.0 (log utility).

evaluate(probability: float, current_bankroll: float) → float[source]

Calculate the optimal Kelly bet size.

The Kelly Criterion formula is: f* = p / l - q / a

where: f* = fraction of current bankroll to bet a = payoff after transaction costs l = loss including transaction costs p = probability of winning q = probability of losing (1 - p)

Unlike the classic formula, which assumes the entire stake is lost, this formula explicitly accounts for the loss multiplier used by this library.

The gate is edge-aware: a bet the formula sizes positively is never silently zeroed. When min_probability is set and the probability falls below it, a formula-positive bet is refused with a warning naming the suppressed fraction; a formula-negative bet refuses silently, because the formula itself declines it.

Parameters:
  • probability (float) – The probability of a successful outcome, typically between 0 and 1.

  • current_bankroll (float) – The current bankroll amount.

Returns:

The optimal proportion of the bankroll to bet.

Return type:

float

Fractional Kelly Criterion

class keeks.binary_strategies.kelly.FractionalKellyCriterion(payoff: float, loss: float, transaction_cost_rate: float, fraction: float)[source]

Bases: 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_rate (float) – The transaction cost as a fraction of each unit staked (per-unit, not a fixed per-transaction amount).

  • fraction (float) – The fraction of the full Kelly bet to use (typically between 0 and 1).

calculate_max_entry_price(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, tolerance: float = 0.01, max_search_fraction: float = 0.5) → float[source]

Calculate maximum price willing to pay for a one-time gamble.

Fractional Kelly is more conservative than full Kelly, so we scale down the entry price proportionally. This assumes the fraction reflects increased risk aversion beyond log utility.

Parameters:
  • outcomes (array-like) – The possible payoffs from the gamble

  • probabilities (array-like) – The probability of each outcome (must sum to ≤ 1)

  • current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.

  • tolerance (float, default=0.01) – Convergence tolerance for binary search. Must be finite and greater than 0.

  • max_search_fraction (float, default=0.5) – Maximum fraction of wealth to consider as upper bound. Must be finite and non-negative; values above 1.0 are allowed.

Returns:

Maximum price willing to pay for the gamble

Return type:

float

Raises:

ValueError – If the gamble arrays are malformed, or if any scalar control falls outside the ranges documented above.

Notes

Fractional Kelly (e.g., Half Kelly) is more conservative than full Kelly. We scale the entry price by the fraction, which is a reasonable approximation though not derived from first principles.

evaluate(probability: float, current_bankroll: float) → float[source]

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:

The optimal proportion of the bankroll to bet, multiplied by the fraction parameter.

Return type:

float

Drawdown-Adjusted Kelly

class keeks.binary_strategies.kelly.DrawdownAdjustedKelly(payoff: float, loss: float, transaction_cost_rate: float, max_transaction_loss: float = 0.2)[source]

Bases: BaseStrategy

Implementation of the Drawdown-Adjusted Kelly Criterion for binary betting.

This strategy adjusts the Kelly bet size based on the maximum per-bet transaction loss it tolerates. 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_rate (float) – The transaction cost as a fraction of each unit staked (per-unit, not a fixed per-transaction amount).

  • max_transaction_loss (float) – The maximum transaction loss tolerated per bet, as a fraction of the bankroll (0 to 1, exclusive) - a per-removal cap the sizing scale keys off, not peak-to-trough drawdown monitoring.

calculate_max_entry_price(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, tolerance: float = 0.01, max_search_fraction: float = 0.5) → float[source]

Calculate maximum price willing to pay for a one-time gamble.

Drawdown-Adjusted Kelly scales down Kelly based on drawdown tolerance. We apply the same scaling factor to the entry price.

Parameters:
  • outcomes (array-like) – The possible payoffs from the gamble

  • probabilities (array-like) – The probability of each outcome (must sum to ≤ 1)

  • current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.

  • tolerance (float, default=0.01) – Convergence tolerance for binary search. Must be finite and greater than 0.

  • max_search_fraction (float, default=0.5) – Maximum fraction of wealth to consider as upper bound. Must be finite and non-negative; values above 1.0 are allowed.

Returns:

Maximum price willing to pay for the gamble

Return type:

float

Raises:

ValueError – If the gamble arrays are malformed, or if any scalar control falls outside the ranges documented above.

Notes

Uses the same drawdown adjustment factor as the betting strategy: drawdown_factor = min(1.0, max_transaction_loss / 0.5)

evaluate(probability: float, current_bankroll: float) → float[source]

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:

The drawdown-adjusted proportion of the bankroll to bet.

Return type:

float

OptimalF

class keeks.binary_strategies.simple.OptimalF(payoff: float, loss: float, transaction_cost_rate: float, win_rate: float, max_risk_fraction: float = 0.2)[source]

Bases: BaseStrategy

Implementation of the Optimal f strategy for binary betting.

This strategy calculates the optimal fraction of bankroll to bet based on the win rate and payoff ratio. It’s similar to Kelly but uses a different approach to risk management.

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_rate (float) – The transaction cost as a fraction of each unit staked (per-unit, not a fixed per-transaction amount).

  • win_rate (float) – The historical or expected win rate (between 0 and 1).

  • max_risk_fraction (float, default=0.2) – The maximum fraction of bankroll that can be risked on a single bet.

calculate_max_entry_price(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, tolerance: float = 0.01, max_search_fraction: float = 0.5) → float[source]

Calculate maximum price willing to pay for a one-time gamble.

Optimal F maximizes geometric growth (like Kelly), so we use log utility.

Parameters:
  • outcomes (array-like) – The possible payoffs from the gamble

  • probabilities (array-like) – The probability of each outcome (must sum to ≤ 1)

  • current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.

  • tolerance (float, default=0.01) – Convergence tolerance for binary search. Must be finite and greater than 0.

  • max_search_fraction (float, default=0.5) – Maximum fraction of wealth to consider as upper bound. Must be finite and non-negative; values above 1.0 are allowed.

Returns:

Maximum price willing to pay for the gamble

Return type:

float

Raises:

ValueError – If the gamble arrays are malformed, or if any scalar control falls outside the ranges documented above.

Notes

Optimal F, like Kelly Criterion, aims to maximize geometric growth, which corresponds to log utility (γ=1.0).

evaluate(probability: float, current_bankroll: float) → float[source]

Calculate the optimal f bet size based on win rate and payoff ratio.

This implementation follows Ralph Vince’s approach to Optimal f, which is based on maximizing the terminal wealth relative (TWR) for a given risk-to-reward profile.

Parameters:
  • probability (float) – The probability of a successful outcome, used to decide whether to bet. Probabilities below 0.5 return zero; bet sizing uses win_rate.

  • current_bankroll (float) – The current bankroll amount.

Returns:

The optimal proportion of the bankroll to stake: Ralph Vince’s optimal f converted from a risk fraction to a stake fraction. The two coincide only when loss + transaction_cost_rate is 1.

Return type:

float

Merton Share

class keeks.binary_strategies.simple.MertonShare(payoff: float, loss: float, transaction_cost_rate: float, risk_aversion: float = 2.0, min_probability: float = 0.5, max_fraction: float = 1.0)[source]

Bases: BaseStrategy

Implementation of the Merton Share strategy using CRRA utility.

This strategy is based on Robert Merton’s portfolio optimization problem [1] with Constant Relative Risk Aversion (CRRA) utility. The optimal fraction to invest is proportional to the expected excess return divided by the product of risk aversion and variance.

The formula is: f* = μ / (γ × σ²) [2]

Where: - f* is the optimal fraction of wealth to invest - μ is the expected excess return (return above risk-free rate) - γ (gamma) is the coefficient of relative risk aversion - σ² (sigma squared) is the variance of returns

For binary betting, we adapt this by: - Expected return is calculated from probability and payoff/loss ratios - Variance is estimated from the binary outcome distribution - Transaction costs are incorporated into the expected return

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_rate (float) – The transaction cost as a fraction of each unit staked (per-unit, not a fixed per-transaction amount).

  • risk_aversion (float, default=2.0) – The coefficient of relative risk aversion (γ). Common values: - 1.0: Low risk aversion - 2.0: Moderate risk aversion (most common empirical estimate) - 3.0-5.0: High risk aversion

  • min_probability (float, default=0.5) – The minimum probability required to place a bet.

  • max_fraction (float, default=1.0) – The maximum fraction of bankroll to bet (safety cap).

References

calculate_max_entry_price(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, tolerance: float = 0.01, max_search_fraction: float = 0.5) → float[source]

Calculate maximum price willing to pay for a one-time gamble.

MertonShare is derived from CRRA utility maximization with the risk_aversion parameter (γ) that was set during initialization.

Parameters:
  • outcomes (array-like) – The possible payoffs from the gamble

  • probabilities (array-like) – The probability of each outcome (must sum to ≤ 1)

  • current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.

  • tolerance (float, default=0.01) – Convergence tolerance for binary search. Must be finite and greater than 0.

  • max_search_fraction (float, default=0.5) – Maximum fraction of wealth to consider as upper bound. Must be finite and non-negative; values above 1.0 are allowed.

Returns:

Maximum price willing to pay for the gamble

Return type:

float

Raises:

ValueError – If the gamble arrays are malformed, or if any scalar control falls outside the ranges documented above.

Notes

Uses the risk_aversion (γ) parameter set during initialization. For γ=1.0, this is equivalent to Kelly Criterion (log utility). Higher γ values indicate more risk aversion and lower willing payments.

evaluate(probability: float, current_bankroll: float) → float[source]

Calculate the Merton Share bet size using CRRA utility.

Parameters:
  • probability (float) – The probability of a successful outcome, typically between 0 and 1.

  • current_bankroll (float) – The current bankroll amount.

Returns:

The optimal proportion of the bankroll to bet based on Merton’s formula.

Return type:

float