footix.strategy package

Submodules

footix.strategy.bets module

class footix.strategy.bets.Bet(match_id, market, odds, prob_mean, edge_std=None, prob_edge_pos=None, stake=0.0)[source]

Bases: object

Represents a single betting opportunity with associated edge information.

Parameters:
match_id

Identifier for the match.

Type:

str

market

Market selection — ‘H’ for home, ‘D’ for draw, ‘A’ for away.

Type:

str

odds

Decimal odds offered by the bookmaker.

Type:

float

edge_mean

Estimated edge over the bookmaker. Computed as (p*(odds-1) - (1-p))

Type:

float

prob_mean

Estimated probability of the event occurring based on the model.

Type:

float

edge_std

Standard deviation of the edge estimate.

Type:

Optional[float]

prob_edge_pos

Probability that the edge is positive (i.e., a value bet).

Type:

Optional[float]

stake

The stake of the bet. By default stake = 0.

Type:

float

match_id: str
market: str
odds: float
prob_mean: float
edge_std: float | None = None
prob_edge_pos: float | None = None
stake: float = 0.0
edge_mean: float
to_dict()[source]
Return type:

dict

classmethod combine_many(bets)[source]

Combines multiple independent bets into a single combined bet (accumulator).

Parameters:

bets (list[Bet]) – List of Bet instances to combine.

Returns:

A new Bet representing the combined bet.

Return type:

Bet

class footix.strategy.bets.OddsInput(home_team, away_team, odds)[source]

Bases: object

Represents the input odds for a match.

Parameters:
home_team

Name of the home team.

Type:

str

away_team

Name of the away team.

Type:

str

odds

Decimal odds in the format [H, D, A], where: - H: Odds for the home team to win. - D: Odds for a draw. - A: Odds for the away team to win.

Type:

list[float]

home_team: str
away_team: str
odds: list[float]
property odd_dict: dict[str, float]

Returns a dictionary mapping the outcomes of a match to their respective odds.

The dictionary contains the following keys: - “H”: Home team win odds - “D”: Draw odds - “A”: Away team win odds

Returns:

A dictionary where the keys are the outcomes (“H”, “D”, “A”) and the values are the corresponding odds as floats.

Return type:

dict[str, float]

property match_id: str

footix.strategy.kelly_strategies module

footix.strategy.kelly_strategies.classic_kelly(list_bets, bankroll)[source]

Compute bet stakes based on the classic Kelly criterion.

This function calculates the stake for each bet in the input list using the traditional Kelly formula. For each bet, it determines the Kelly fraction as the ratio of the estimated edge (i.e., the advantage) over the net odds (odds - 1). Negative Kelly fractions are set to zero, ensuring no bet is made when the edge is unfavorable. The final stake for each bet is computed by multiplying the Kelly fraction by the available bankroll.

Parameters:
  • list_bets (list[Bet]) – A list of Bet objects. Each Bet must have an ‘odds’ attribute representing the betting odds and an ‘edge_mean’ attribute indicating the estimated edge.

  • bankroll (float) – The total available bankroll to allocate across the bets.

Returns:

The list of Bet objects with their ‘stake’ attribute updated according to

the Kelly criterion.

Return type:

list[Bet]

footix.strategy.kelly_strategies.realKelly(list_bets, bankroll, max_multiple=1, num_iterations=1000, learning_rate=0.1, penalty_weight=1000.0, device='cpu', early_stopping=True, tolerance=5)[source]

Compute the Kelly criterion using a GPU accelerated gradient-based optimizer (PyTorch).

Parameters:
  • list_bets (list[dict[str, Any]]) – List of bets.

  • bankroll (float) – Total bankroll available.

  • max_multiple (int, optional) – Maximum number of selections to combine. Defaults to 1.

  • num_iterations (int, optional) – Number of iterations for gradient descent.

  • learning_rate (float, optional) – Learning rate for the optimizer.

  • penalty_weight (float, optional) – Weight for the penalty term enforcing the bankroll constraint.

  • device (str, optional) – Device to run the computations on (“cuda” or “cpu”).

  • early_stopping (bool, optional) – Whether to stop early if convergence is detected.

  • tolerance (int, optional) – Tolerance for early stopping.

Returns:

A dictionary containing, for each bet with non-negligible stake,

the bet string, the bet odd, and the stake.

Return type:

list[dict]

footix.strategy.kelly_strategies.bayesian_kelly(list_bets, lambda_samples, bankroll=100, summary='quantile', alpha=0.3, fraction_kelly=0.5, per_bet_cap=0.9)[source]

Compute the optimal bet sizes using a Bayesian Kelly criterion.

This function uses Monte Carlo samples of the scoring intensities (lambda samples) to estimate the posterior probabilities of match outcomes via a Skellam model. It then computes the Kelly fraction for each bet based on either the mean or a given quantile of the computed fractions. A fraction of the Kelly stake is applied along with per-bet capping.

Parameters:
  • list_bets (list[Bet]) – List of bet objects for which to compute the stake.

  • lambda_samples (dict[str, SampleProbaResult) – Mapping of match IDs to a tuple of numpy arrays representing the lambda samples for home and away teams.

  • bankroll (float, optional) – Total available bankroll. Defaults to 100.

  • summary (Literal["mean", "quantile"], optional) – Method to summarize the Kelly fraction computation. If ‘mean’, the average is used; if ‘quantile’, the quantile specified by alpha is used. Defaults to “quantile”.

  • alpha (float, optional) – Quantile level used if summary is “quantile”. Defaults to 0.3.

  • fraction_kelly (float, optional) – Proportion of the calculated Kelly stake to bet. Defaults to 0.5.

  • per_bet_cap (float, optional) – Maximum allowed fraction of the bankroll to wager

  • 0.90. (on a single bet. Defaults to)

Raises:

ValueError – If the summary parameter is not ‘mean’ or ‘quantile’.

Returns:

List of bet objects with the calculated stake assigned.

Return type:

list[Bet]

footix.strategy.kelly_strategies.kelly_shrinkage(list_bets, lambda_samples, per_bet_cap=0.1, bankroll_cap=0.3, bankroll=100, fraction_kelly=0.25)[source]

Compute bet sizes using a shrinkage-adjusted Kelly criterion.

This function calculates the bet stake by applying a shrinkage correction to the classic Kelly criterion. For each bet, the posterior probability is estimated using a Skellam model with lambda samples and its mean and variance are computed. A shrinkage factor is derived from these statistics and is used to adjust the full Kelly fraction. The computed fraction is then capped per bet and scaled by the available bankroll. An overall bankroll cap is also enforced if the sum of bet fractions exceeds a predefined threshold.

Parameters:
  • list_bets (list[Bet]) – List of bet objects to evaluate.

  • lambda_samples (dict[str, SampleProbaResult) – Dictionary mapping match IDs to probabilities of sampled MCMC trajectory for the game.

  • per_bet_cap (float, optional) – Maximum fraction of the bankroll allowed on a single bet.

  • 0.10. (Defaults to)

  • bankroll_cap (float, optional) – Maximum allowed total fraction of the bankroll across

  • 0.30. (all bets. Defaults to)

  • bankroll (float, optional) – Total available bankroll. Defaults to 100.

  • fraction_kelly (float, optional) – Fraction of the shrinkage-adjusted Kelly stake to wager.

  • 0.25. (Defaults to)

Returns:

List of bet objects with their stake updated according to the shrinkage-adjusted Kelly criterion.

Return type:

list[Bet]

footix.strategy.kelly_strategies.kelly_portfolio_torch(list_bets, lambda_samples, *, per_bet_cap=0.1, bankroll_cap=0.3, bankroll=100.0, fraction_kelly=0.25, iters=5000, lr=0.003, tol=1e-08, verbose=False)[source]

Compute optimal bet sizes using a shrinkage-adjusted Kelly criterion with gradient-based optimization in PyTorch.

This function calculates bet stakes by applying a shrinkage correction to the classic Kelly criterion. It uses gradient ascent to optimize the portfolio allocation while respecting per-bet and total bankroll constraints.

Parameters:
  • list_bets (list[Bet]) – List of bet objects to evaluate.

  • lambda_samples (dict[str, SampleProbaResult) – Dictionary mapping match IDs to probabilities samples for the game.

  • per_bet_cap (float, optional) – Maximum fraction of the bankroll allowed on a single bet. Defaults to 0.10.

  • bankroll_cap (float, optional) – Maximum allowed total fraction of the bankroll across all bets. Defaults to 0.30.

  • bankroll (float, optional) – Total available bankroll. Defaults to 100.0.

  • fraction_kelly (float, optional) – Fraction of the shrinkage-adjusted Kelly stake to wager. Defaults to 0.25.

  • iters (int, optional) – Maximum number of iterations for gradient ascent. Defaults to 5000.

  • lr (float, optional) – Learning rate for the optimizer. Defaults to 0.003.

  • tol (float, optional) – Tolerance for early stopping. If the objective value falls below this threshold, the optimization stops. Defaults to 1e-8.

  • verbose (bool, optional) – If True, prints detailed information about the bankroll usage and possible returns. Defaults to False.

Returns:

List of bet objects with their stake updated according to the shrinkage-adjusted Kelly criterion.

Return type:

list[Bet]

Notes

  • The shrinkage factor is derived from the mean and variance of the posterior probabilities, ensuring more robust stake calculations.

  • The optimization respects both per-bet and total bankroll constraints, projecting the solution back into the feasible region when necessary.

  • The function uses PyTorch for efficient gradient-based optimization.

Example

>>> bets = [Bet(match_id="match1", market="H", odds=2.5), ...]
>>> lambda_samples = {"match1": (np.array([...]), np.array([...]))}
>>> optimized_bets = kelly_portfolio_torch(
        bets, lambda_samples, bankroll=100, verbose=True
    )
>>> for bet in optimized_bets:
>>>     print(bet)

footix.strategy.portfolio_management module

footix.strategy.portfolio_management.stack_bets(bets)[source]

Computes the mean and standard deviation of the edges for a list of bets.

Parameters:

bets (list[Bet]) – A list of Bet objects, where each Bet contains attributes edge_mean (float) and edge_std (float or None).

Returns:

A tuple containing two numpy arrays:
  • The first array contains the mean edge values.

  • The second array contains the standard deviation of the edge values, with missing values replaced by 0.0.

Return type:

tuple[np.ndarray, np.ndarray]

footix.strategy.portfolio_management.stack_markowitz(bets)[source]

Computes the mean and standard deviation of the bet (and not from the model)

Parameters:

bets (list[Bet]) – A list of Bet objects, where each Bet contains attributes

Returns:

A tuple containing two numpy arrays:
  • The first array contains the mean edge values.

  • The second array contains the standard deviation

Return type:

tuple[np.ndarray, np.ndarray]

footix.strategy.portfolio_management.optimise_portfolio(list_bets, bankroll, max_fraction=0.3, alpha=0.05, gamma=None)[source]

Optimizes bet stakes using SciPy’s constrained optimization to maximize return with risk control.

This function uses classical constrained optimization to find the optimal stake allocation that maximizes expected value while maintaining risk constraints. It incorporates Shannon entropy to encourage diversification.

Parameters:
  • list_bets (list[Bet]) – List of bets to optimize. Each Bet must have edge_mean and edge_std.

  • bankroll (float) – Total available funds for betting.

  • max_fraction (float, optional) – Maximum fraction of bankroll to stake. Defaults to 0.30.

  • alpha (float, optional) – Risk threshold for chance constraint (probability of loss). Defaults to 0.05.

  • gamma (float | None, optional) – Entropy bonus weight. If None, defaults to 0.9 * stake_cap. Controls diversification strength.

Returns:

Input bets with optimized stakes set in their stake attribute.

Return type:

list[Bet]

Raises:

RuntimeError – If the optimization fails to converge to a valid solution.

Notes

  • Uses trust-region constrained optimization from SciPy

  • Enforces two main constraints:
    1. Total stakes ≤ max_fraction * bankroll

    2. P(portfolio loss) ≤ alpha via chance constraint

footix.strategy.portfolio_management.optimise_portfolio_torch(list_bets, bankroll, max_fraction=0.3, alpha=0.05, gamma=None, lr=0.05, iters=5000, penalty_lambda=1000.0, verbose=False, device='cpu')[source]

Optimizes bet stakes using PyTorch’s gradient descent with soft constraints.

This function implements portfolio optimization using automatic differentiation and gradient descent. Instead of hard constraints, it uses soft constraints via penalty terms in the loss function.

Parameters:
  • list_bets (list[Bet]) – List of bets to optimize. Each Bet must have edge_mean and edge_std.

  • bankroll (float) – Total available funds for betting.

  • max_fraction (float, optional) – Maximum fraction of bankroll to stake. Defaults to 0.30.

  • alpha (float, optional) – Risk threshold for chance constraint. Defaults to 0.05.

  • gamma (float | None, optional) – Entropy bonus weight. If None, defaults to 0.9 * stake_cap.

  • lr (float, optional) – Learning rate for Adam optimizer. Defaults to 5e-2.

  • iters (int, optional) – Number of optimization iterations. Defaults to 5_000.

  • penalty_lambda (float, optional) – Weight of chance constraint penalty. Defaults to 1_000.0.

  • verbose (bool, optional) – Whether to print diagnostic information. Defaults to False.

  • device (str, optional) – PyTorch device to use (‘cpu’ or ‘cuda’). Defaults to “cpu”.

Returns:

Input bets with optimized stakes set in their stake attribute.

Return type:

list[Bet]

Notes

  • Uses Adam optimizer with gradient descent

  • Enforces constraints softly through penalties:
    1. Stakes positivity via softplus

    2. Total stakes via scaling

    3. Risk control via quadratic penalty

  • Includes Shannon entropy term for diversification

  • Provides detailed diagnostics when verbose=True

footix.strategy.portfolio_management.bayesian_portfolio_optim(list_bets, edge_samples, bankroll, max_fraction=0.3, gamma=None, lr=0.05, iters=3000, verbose=False, device='cpu')[source]

Optimizes bet stakes using Bayesian posterior samples of edge.

This function implements portfolio optimization by maximizing the expected gain marginalized over the uncertainty in edge estimates. It uses Monte Carlo samples from the posterior distribution of edge for each bet.

The optimization maximizes:

E_θ[Gain] = (1/K) * Σ_k Σ_i edge_i^(k) * s_i

where edge_i^(k) is the k-th posterior sample of edge for bet i.

Parameters:
  • list_bets (list[Bet]) – List of bets to optimize. Each Bet must have a match_id and market.

  • edge_samples (Mapping[Any, Any]) – Dictionary mapping (match_id, market) tuple or match_id to numpy arrays of posterior edge samples. Shape: (n_samples,) for each bet.

  • bankroll (float) – Total available funds for betting.

  • max_fraction (float) – Maximum fraction of bankroll to stake. Defaults to 0.30.

  • gamma (float | None) – Entropy bonus weight for diversification. If None, defaults to 0.5 * stake_cap.

  • lr (float) – Learning rate for Adam optimizer. Defaults to 5e-2.

  • iters (int) – Number of optimization iterations. Defaults to 3_000.

  • verbose (bool) – Whether to print diagnostic information. Defaults to False.

  • device (str) – PyTorch device to use (‘cpu’ or ‘cuda’). Defaults to “cpu”.

Returns:

Input bets with optimized stakes set in their stake attribute.

Return type:

list[Bet]

Notes

  • Uses Adam optimizer with gradient descent

  • Enforces constraints softly through scaling:
    1. Stakes positivity via softplus

    2. Total stakes via scaling to respect stake_cap

  • Includes Shannon entropy term for diversification

  • Marginalizes over posterior uncertainty in edge estimates

Example

>>> # edge_samples[bet.match_id] contains posterior samples for each outcome
>>> edge_samples = {
...     ("match1", "H"): np.random.normal(0.1, 0.05, 1000),
...     ("match1", "D"): np.random.normal(-0.05, 0.03, 1000),
... }
>>> optimized_bets = bayesian_portfolio_optim(bets, edge_samples, bankroll=1000)
footix.strategy.portfolio_management.mean_variance_markowitz(list_bets, bankroll, risk_aversion=1.0, lr=0.01, iters=5000, device='cpu', verbose=False)[source]
Parameters:

footix.strategy.select_bets module

class footix.strategy.select_bets.Thresholds(edge_floor, prob_edge)[source]

Bases: NamedTuple

Parameters:
edge_floor: float

Alias for field number 0

prob_edge: float | None

Alias for field number 1

class footix.strategy.select_bets.OddsRange(min_odds, max_odds, edge, prob_edge=None)[source]

Bases: NamedTuple

Represents an odds range and its corresponding edge and probability thresholds.

Parameters:
min_odds

Minimum odds value (inclusive)

Type:

float

max_odds

Maximum odds value (inclusive)

Type:

float

edge

Required edge floor for this odds range

Type:

float

prob_edge

Required probability of positive edge (optional)

Type:

float | None

min_odds: float

Alias for field number 0

max_odds: float

Alias for field number 1

edge: float

Alias for field number 2

prob_edge: float | None

Alias for field number 3

class footix.strategy.select_bets.EdgeFloorConfig(ranges: Sequence[footix.strategy.select_bets.OddsRange] = <factory>, default_edge_floor: float = 0.0, default_prob_edge: float | None = None)[source]

Bases: object

Parameters:
ranges: Sequence[OddsRange]
default_edge_floor: float
default_prob_edge: float | None
get_thresholds(odds)[source]

Return edge-floor thresholds for the given odds.

Parameters:

odds (float)

Return type:

Thresholds

footix.strategy.select_bets.simple_select_bets(odds_input, probas, edge_floor=0.0, single_bet_per_game=True, outcomes=('H', 'D', 'A'))[source]
Parameters:
Return type:

list[Bet]

footix.strategy.select_bets.select_matches_posterior(odds_input, lambda_samples, *, config=None, edge_floor=0.1, prob_edge_threshold=0.55, single_bet_per_game=True)[source]

Select bets based on posterior probabilities computed from the Skellam distribution.

For each match, posterior probabilities for the home-win, draw, and away-win outcomes are computed. The expected edge is calculated for each outcome. Bets are only selected if the mean edge exceeds the specified edge_floor and the probability of a positive edge is above the prob_edge_threshold. If single_bet_per_game is True, only the bet with the highest mean edge is kept per match.

Parameters:
  • odds_input (list[OddsInput]) – List of odds input objects.

  • lambda_samples (dict[str, tuple[np.ndarray, np.ndarray]]) – Dictionary mapping match_id to lambda samples (home and away) used for posterior probability computation.

  • edge_floor (float, optional) – Minimum required mean edge to consider a bet.

  • 0.1. (Defaults to)

  • prob_edge_threshold (float, optional) – Minimum probability of positive edge to consider a bet. Defaults to 0.55.

  • single_bet_per_game (bool, optional) – If True, only the best bet per match is

  • True. (selected. Defaults to)

  • config (EdgeFloorConfig | None)

Returns:

A sorted list of selected Bet objects, ordered by descending edge_mean.

Return type:

list[Bet]

footix.strategy.select_bets.select_bets_by_probability(odds_input, probas, prob_floor=0.55, single_bet_per_game=True)[source]

Select bets based on the highest predicted probabilities.

For each match, outcomes with predicted probability greater than or equal to prob_floor are considered. If single_bet_per_game is True, only the outcome with the highest probability (if it meets the threshold) is selected per match. Otherwise, every outcome meeting the threshold is selected.

Parameters:
  • odds_input (list[OddsInput]) – List of odds input objects.

  • probas (np.ndarray) – Array of shape (n_matches, 3) containing predicted probabilities.

  • prob_floor (float, optional) – Minimum acceptable probability for a bet. Defaults to 0.55.

  • single_bet_per_game (bool, optional) – If True, only the most probable bet per match is selected. Defaults to True.

Returns:

A list of Bet objects selected based on the highest probability.

Return type:

list[Bet]

footix.strategy.simple_strategy module

footix.strategy.simple_strategy.flat_staking(list_bets, bankroll, fraction_bankroll)[source]

Allocate a fixed portion of the bankroll to each bet.

The stake for every bet in list_bets is set to fraction_bankroll * bankroll. This simple staking strategy assumes that all bets are independent and that the same fraction of the bankroll is used for each one.

Parameters:
  • list_bets (list[Bet]) – A list of Bet objects whose stake attribute will be updated.

  • bankroll (float) – The total amount of money available to stake.

  • fraction_bankroll (float) – The fraction of the bankroll to allocate to each bet. Must satisfy fraction_bankroll * len(list_bets) <= 1.

Returns:

The input list with the stake attribute updated for each bet.

Return type:

list[Bet]

Raises:

ValueError – If the total required stake exceeds the available bankroll.

Module contents

Betting strategy and portfolio management utilities.

This module provides tools for developing, backtesting, and executing betting strategies based on model predictions and market odds.

Submodules:
  • bets: Core betting and odds structures

  • select_bets: Bet selection and filtering logic

  • kelly_strategies: Kelly criterion-based strategies

  • portfolio_management: Portfolio optimization and management