Introducing keeks-elote

I maintain two libraries that were always going to have to meet. elote rates competitors and turns ratings into probabilities. keeks turns probabilities and prices into stakes. Between them is a gap that everyone who has tried this fills with a script.

keeks-elote is that script, made into something you can install, with its ordering guarantees written down.

What it does

from elote.arenas.lambda_arena import LambdaArena
from elote.competitors.massey import MasseyCompetitor
from keeks.bankroll import BankRoll
from keeks.binary_strategies import KellyCriterion
from keeks_elote import Backtest

arena = LambdaArena(lambda a, b: True, base_competitor=MasseyCompetitor)
bank = BankRoll(initial_funds=10_000, percent_bettable=0.5, max_draw_down=1.0)
strategy = KellyCriterion(payoff=1.0, loss=1.0, transaction_cost=0.0)

Backtest(arena).run_explicit(games_by_week, strategy, bank, period_to_start_betting=4)

games_by_week is a dict keyed by period, each holding game dicts with a winner, a loser, and optionally American moneylines and scores. That is the whole interface. Everything else is the two libraries you already have.

The seam is a time-ordering problem

The reason this is a library rather than twenty lines is that the interesting part is not the arithmetic, it is the sequencing. Each period runs in a fixed order:

  1. Price next period’s games from the ratings as they stand now
  2. Place those bets
  3. Settle them
  4. Then update the ratings with the period’s results

Get steps 1 and 4 the wrong way round and you have a backtest that prices Saturday’s games using Saturday’s results. It will produce spectacular returns and mean nothing, and nothing in the output will look wrong, because every individual step is correct.

Three more ordering rules are enforced inside a period, and each of them is a way a backtest can cheat without you noticing:

Every wager in a period is sized from one snapshot of the opening bankroll. If bets settle one at a time and each is sized from the running balance, then the second bet of the week is sized using information from the first, which you did not have when you placed it. Sizing simultaneous bets sequentially inflates returns and does it invisibly.

The period’s total exposure is capped, not each bet individually. A strategy quoting a fraction per game has no idea how many other games it is being asked about, so a confident week routinely asks to stake several times the bankroll. Clamping each bet against the funds still live lets the earliest games consume everything and starves the rest, which is a decision made by the fixture list rather than by the strategy. Requested stakes that exceed the budget are scaled proportionally instead, preserving the relative sizing the strategy asked for.

Stakes are placed at the fraction the strategy actually quoted, not a rounded or re-derived one.

None of that is clever. All of it is the difference between a backtest you can believe and one you cannot.

Margins reach the rating systems

Rating systems that model margin of victory need the scores, not just who won. The arena update forwards them when a game carries them:

{"winner": "Auburn", "loser": "Alabama", "winner_score": 26, "loser_score": 14,
 "winner_odds": +150, "loser_odds": -180}

It matters. Massey’s top five for the 2017 season, with margins and without:

with margins : Penn St, Ohio St, Alabama, Clemson, Georgia
win/loss only: Clemson, Georgia, Ohio St, UCF, Miami FL

Win/loss-only Massey has 13-0 UCF fourth. Margin-aware Massey does not have them in the top five, which is the right read of a perfect record against that schedule.

Games without scores keep working, and a score that is missing, unparseable, or that contradicts the recorded winner falls back to rating on the result alone. That last case is not hypothetical: 51 of the 5,100 games in the bundled example data carry 0-0 as a placeholder for a score nobody recorded.

What it is for

Honestly, mostly for finding out that things do not work. The college football backtest runs the best rating system in elote against a season of real moneylines across twelve staking strategies, and every one of the thirty-six combinations loses money. The reason is visible in the output rather than a matter of opinion: where the model most disagrees with the closing line is where it is most wrong.

That is a useful thing for a library to make easy. Coupling a rating model to a staking model is a small enough job that people do it by hand, get an encouraging number, and have no way to tell whether the number came from the model or from the ordering. Having the ordering fixed and the metrics reported means the answer, when it comes back negative, is trustworthy.

Installing

pip install keeks-elote

It brings elote>=1.2.0 and keeks>=0.3.0 with it, requires Python 3.10 or newer, and the college football example in examples/ runs the whole stack end to end on real season data.