Skip to main content

Keeks-Elote

License: MIT

A Python library integrating the elote rating system library and the keeks bankroll management library to facilitate backtesting and evaluation of combined ranking and betting strategies.

Purpose

The primary goal of keeks-elote is to provide a framework for simulating and analyzing the performance of different rating algorithms (like Elo, Glicko, etc.) when coupled with various bankroll management strategies (like Kelly Criterion, fixed betting, etc.). This allows users to explore how prediction accuracy from rating systems translates into profitability under different staking plans in competitive scenarios (e.g., sports betting, gaming).

Why is this interesting? (Features)

  • Integration: Seamlessly combines rating generation (elote) with betting strategy simulation (keeks).
  • Backtesting Framework: Provides tools to run historical simulations on outcome data.
  • Flexibility: Supports multiple rating systems and bankroll management techniques available in the underlying libraries.
  • Evaluation: Enables analysis of strategy performance based on metrics like profit/loss, ROI, etc.
  • Extensibility: Designed to be potentially extended with custom rating models or betting strategies.

Installation

pip install keeks-elote

Or from source:

git clone https://github.com/wdm0006/keeks-elote.git
cd keeks-elote
pip install -e .

For development, clone the repository and install in editable mode with development dependencies:

git clone https://github.com/wdm0006/keeks-elote.git
cd keeks-elote
pip install -e .[dev]

How to Use It

The core idea is to use elote to generate ratings and predictions based on historical match/game data and then use keeks to simulate betting on those predictions according to a chosen bankroll strategy.

You provide historical outcomes as a Dict[int, List[dict]] keyed by period (e.g. week). Each game dict needs winner and loser labels, plus optional winner_odds/loser_odds in American or decimal odds -- the format is detected per price (American when negative or at least 100 in magnitude, decimal otherwise) -- and bets are only placed on games that include odds:

from keeks.bankroll import BankRoll
from keeks.binary_strategies.kelly import KellyCriterion

from keeks_elote import Backtest, create_arena

# Historical outcomes, keyed by period (e.g. week). Ratings update from the
# known winner/loser; odds drive the simulated bets in later periods.
data = {
    1: [{"winner": "Alabama", "loser": "Auburn", "winner_odds": -150, "loser_odds": 130}],
    2: [{"winner": "Georgia", "loser": "Florida", "winner_odds": -200, "loser_odds": 175}],
    # ... more periods ...
}

# The arena generates ratings and predictions from the game records. Every game's
# recorded winner is always forwarded to the ratings update, so the built-in
# comparison function is never asked to decide a result the data already knows.
arena = create_arena("glicko")

# The bankroll and a betting strategy from keeks.
bankroll = BankRoll(initial_funds=10000, percent_bettable=0.5, max_draw_down=1.0)
strategy = KellyCriterion(payoff=1.0, loss=1.0, transaction_cost=0.0)

# Periods up to `period_to_start_betting` are dry runs that only build ratings;
# real bets begin after it. Returns the updated bankroll.
backtest = Backtest(arena)
result = backtest.run_explicit(data, strategy, bankroll, period_to_start_betting=1)
print(result.total_funds)

# Every wager the run settled is also recorded, so a comparison run can be read
# beyond its closing balance.
print(len(backtest.bet_history))

The closing total_funds conflates hit rate, stake sizing and how many bets were even placed, so run_explicit also fills Backtest.bet_history: one dict per wager the run considered, settled or not, carrying period, label, opponent, fraction, the stake actually placed (after the period's exposure scaling and any clamp against bettable funds), payoff, won, profit and bankroll_after. Candidates that moved no money are recorded too, flagged with skipped_zero_stake or error, so every bet the run considered is accounted for. The list is cleared at the start of each run_explicit call, so reusing a Backtest never mixes two runs. Aggregations ship with the package: pnl(bet_history) nets the run's profit and roi(bet_history) reports it per unit staked, and anything more specific (hit rate, drawdown) stays one line of caller code over the ledger.

Failures are part of that accounting: a strategy that raises while pricing a candidate is recorded the moment it fails (fraction of None plus the error message), and Backtest.run_summary() condenses the ledger into counts -- placed, failed with their reasons, skipped, wins/losses and net profit -- so a systematically broken strategy reads as failed_bets: N, never as an empty, plausible-looking run.

Strategies that maintain state through keeks' record_result(won, return_pct) hook -- DynamicBankrollManagement's streak and volatility windows, for example -- are notified of every bet the run actually settles, so their sizing adapts as the backtest progresses. Strategies without the hook are unaffected, and stateful strategies are always notified on the instance you passed in, even when each bet is priced by a freshly constructed re-priced copy. The notification is skipped for candidates that moved no money (a zero stake or a failed settlement), since there is no settled result to record.

The one-liner and the rest of the public surface

create_arena maps a rating system's name to its elote competitor class, so the arena setup is one line. Supported names: bradley-terry, colley, dwz, ecf, elo, glicko, glicko2, keener, massey, pythagorean, trueskill, and whr. Keyword arguments flow through: base_kwargs configures the rating system's competitor (for example {"initial_rating": 2100}), and any other keyword argument (elote's func, initial_state) is forwarded to the arena.

from keeks_elote import create_arena

arena = create_arena("glicko")

An unknown name raises a ValueError listing the supported ones. The package root also re-exports the functions the backtest itself runs on, so a single import covers the whole flow: prepare_data (validates and cleans period-keyed input), load_csv/load_dataframe (build that input from a CSV file or a DataFrame), calculate_probabilities (win probability from the arena), to_decimal (either-format odds conversion, with american_to_decimal for American-only input), edge (expected value per unit staked), pnl/roi (ledger profit and per-unit-staked return), and summarize_bet_history (the ledger aggregation behind run_summary()).

Odds formats and value metrics

Game records accept prices in either format, and to_decimal converts any price explicitly: a negative price or one of magnitude 1prices a wager's expected value per unit staked -- the comparison between the model's win probability and the odds-implied one -- and pnl/roi net a bet_history ledger into its profit and per-unit-staked return.

from keeks_elote import edge, to_decimal

to_decimal(-110)   # 1.909... -- American
to_decimal(1.91)   # 1.91     -- decimal
edge(0.5, 2.1)     # 0.05 -- a half chance at 2.1 wins 5% per unit staked

Loading your own data

load_csv and load_dataframe turn flat rows into the period-keyed dict the backtest consumes: required period/winner/loser columns, optional winner_odds/loser_odds prices and winner_score/loser_score margins, and any extra columns (dates, venues...) carried through untouched. Row content is handled the way prepare_data handles it -- rows missing winner/loser labels are dropped with a warning and unparseable optional numbers drop just that field -- while a row whose period is missing or not an integer raises ValueError naming its line, so a corrupt schedule cannot load half-silently. load_dataframe takes a pandas-style frame (pandas itself is not a dependency; any object with columns and to_dict(orient="records") works):

from keeks_elote import load_csv, load_dataframe

data = load_csv("data/epl_2023_24.csv")   # {1: [{...}, ...], 2: [...], ...}
data = load_dataframe(df)                 # same shape, from a DataFrame

See examples/cfb.py for a complete end-to-end example using real college-football data, examples/epl.py for the same flow over the real 2023-24 Premier League season -- load_csv, create_arena, decimal odds, edge, and the pnl/roi metrics -- and examples/epl_1x2.py for the 1X2 flow below over a synthetic draw-inclusive season.

The 1X2 (home / draw / away) flow

The binary backtest prices one wager per game because its records name a winner and a loser. keeks_elote.multi_outcome_backtest.MultiOutcomeBacktest backtests the whole three-leg book -- home, draw, away -- through keeks' multi-outcome API: the arena's expected score expands into a full (home, draw, away) book with a draw probability that peaks at rating parity, a keeks multi-outcome strategy splits the stake across the legs at each game's own prices, and settlement is simulated -- one categorical draw per game realizes exactly one leg through RepeatedMultiOutcomeSimulator, while the recorded scores rate the teams (draws rate as draws, outcome 0.5) but never decide a bet.

from keeks.bankroll import BankRoll
from keeks.multi_outcome import MultiOutcomeKellyCriterion
from keeks_elote import create_arena, pnl
from keeks_elote.multi_outcome_backtest import MultiOutcomeBacktest

backtest = MultiOutcomeBacktest(create_arena("elo"), draw_rate=0.25)
bankroll = BankRoll(initial_funds=1000.0, percent_bettable=1.0, max_draw_down=None)
backtest.run_explicit(
    data,                                       # period-keyed 1X2 game records
    MultiOutcomeKellyCriterion(payoffs=(2.0, 3.0, 3.0), loss=1.0),
    bankroll,
    period_to_start_betting=2,
    seed=42,                                    # settlement replays deterministically
)
pnl(backtest.bet_history)                       # net profit over the ledger

1X2 records name the sides positionally (home/away) and let the scores speak: 2-2 is a draw, 0-1 an away win. The home_odds/draw_odds/away_odds prices are optional per game and consumed only when all three are present; a game missing any price is rated but not bet. bet_history records the book, the quoted stake fractions, the absolute stakes, the realized leg, and the bankroll reads around every game, so pnl/roi reconcile with the closing balance.

This flow needs keeks' multi_outcome module (keeks >= 0.8.0), which is not on PyPI yet -- install keeks from its git default branch (make install does). Until the keeks floor is bumped in the 0.3.0 release, the module is imported from its path and is not re-exported from the package root, so import keeks_elote keeps working on every released keeks.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Metadata

Release files for keeks-elote 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for keeks-elote 0.3.0
File Size Uploaded Provenance Actions
keeks_elote-0.3.0.tar.gz 468.8 kB With provenance Details for keeks_elote-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for keeks-elote 0.3.0
File Interpreter ABI Platform Actions
keeks_elote-0.3.0-py3-none-any.whl Python 3 none any Details for keeks_elote-0.3.0-py3-none-any.whl

Total release size: 505.5 kB

Release files / keeks_elote-0.3.0.tar.gz

Download URL keeks_elote-0.3.0.tar.gz
Size 468.8 kB
Tags Source
SHA-256 checksum
How to use checksums
5836181976be479dcdd3f4e78b36ed13a73aefedafdc0802c8715b54e1926f53
BLAKE2b-256 checksum
How to use checksums
8f5f5f675ff76ae7a98ea82ff820d63b9e8721395f272bf5138e139fbd999c65
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release files / keeks_elote-0.3.0-py3-none-any.whl

Download URL keeks_elote-0.3.0-py3-none-any.whl
Size 36.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
410eefa0c92f197bcb0684c1d6e2da0b51bc631444342606194e49feafd55e82
BLAKE2b-256 checksum
How to use checksums
55b5afd76173a84e72e9fd178465d02b22ff76b74c7e574de3b36b69502095a7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Security for keeks-elote 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for keeks_elote-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page