CLI reference¶
One command, task first:
racinglines <group> <command> [SPORT] [options]. Every group, every command, its-hand a real run where one is captured.
Everything runs through one command (after pip install "racinglines[db]", or pip install -e ".[web]" in a
clone). The task comes first and the sport is an argument. racinglines -h is the map:
racinglines <group> <command> [SPORT] [options]
data fetch | ingest | link | download | parse | record | pull | ... SPORT (results, sessions, feeds)
model forecast | backtest | compare | diagnostic | props | ... SPORT (pricing)
strategy replay | sweep | season-replay | signals | scorecard | ... SPORT (trading against exchange tapes)
backtest walk-forward SPORT | coverage | clv (the backtest core, any sport with a pricing model)
weather probe | fetch | show (weather forecasts per event: Weather Underground, docs/weather.md)
markets sync | history | trades | record | archive
book map | price | settle | view | render | new (generic sportsbook lines and slips: model and market EV, docs/sportsbook/slips.md)
db init | seed | stats | export
live new | step | run | agent | status | report (a live private-book event, any sport)
web serve the web app
users setup | list | add | reset-password | remove | deactivate | activate (accounts, fantasy bucks)
mcp the MCP server: the data and simulations for a chat client (docs/mcp.md)
remote tools | call | job | logout (the hosted server's tools from the command line, docs/mcp.md)
check quick validation: code on synthetic data, data endpoints, database
SPORT is f1, mtb_dh, nascar, motogp or cycling, e.g. `racinglines model forecast f1 --save`.
--demo on any command: the same command on the demo store, a separate database and data folder of synthetic data
(built on first use; racinglines/cli/source.py, docs/quickstart.md), e.g. `racinglines model forecast kart --demo --save`.
`racinglines <group> -h` lists a group's commands and the sports that offer each.
Add --demo to any command
--demo runs the same command on a synthetic demo store, a separate database and data folder built on first use,
so every example on this page can be tried without data of your own (Quickstart).
The PyPI package covers part of this page
pip install "racinglines[db]" has the F1 and downhill (mtb_dh) commands, the kart demo, book, db,
live, users and check. pip install "racinglines[mcp]" adds mcp, remote and data pull, and
pip install "racinglines[web]" adds web. The
nascar, motogp and cycling sports and the weather and tui commands are in the development repository
only: in the package they stop with an error.
Conventions¶
SPORT is f1, mtb_dh, nascar, motogp or cycling (road_cycling too). racinglines <group> -h lists the
group's commands with the sports that offer each, and racinglines <group> <command> SPORT -h gives a command's
options. Examples:
racinglines data ingest nascar --years 2026
racinglines model forecast f1 --save
racinglines model backtest f1 --variant grid --save # the sport's own options (f1's --variant, --db) go anywhere after it
racinglines strategy replay f1 --runs 1298,1299,1300 --sweep
The old sport-first form (the sport before the command) is deprecated: for one version it still runs and prints a
one-line note on stderr naming the new form, then it is removed. Nothing in the repo calls it
(tests/test_no_old_cli_forms.py checks code, scripts, systemd units and docs). The mapping lives in
racinglines/cli/verbs.py; which sports offer which command is read from each sport's parser
(racinglines/cli/<sport>.py), so a new command only needs a group there.
python -m racinglines … works too.
The database connection comes from $DATABASE_URL, or --db URL (racinglines model forecast f1 --db URL …,
racinglines db --db URL …). The default is the local Postgres on port 5433 (see Database).
Output: Rich, plain, JSON¶
db stats, model backtest f1, model forecast f1, live run, live report and book map|price|settle
print Rich output by default (colour, tables, probability bars, a few emojis for steps and events), also when piped or
recorded; redrawing progress bars only on a terminal (off one, or with TTY_INTERACTIVE=0, each line prints once).
--plain on any command, or RACINGLINES_PLAIN=1, prints the plain text instead; --json is always pure JSON.
NO_COLOR drops every escape code and keeps the layout and emojis; FORCE_COLOR keeps colour. Under systemd
(JOURNAL_STREAM set) the output carries no escape codes, so journalctl never shows [blob data]. The lines scripts
and the Lab read stay plain text in every mode: progress i/n ..., Saved ... run N., Per-race rows -> FILE
(racinglines/cli/tty.py).
| Switch | Effect |
|---|---|
| (default) | Rich: colour, tables, bars |
--plain, RACINGLINES_PLAIN=1 |
the plain text |
--json |
pure JSON (where a command offers it) |
NO_COLOR=1 |
the Rich layout without escape codes (how the outputs on these pages were captured) |
FORCE_COLOR=1 |
colour even when piped |
Where the data comes from: --demo, --remote, --local¶
Three modes on book map|price|settle|export, and --demo and --remote on more commands:
| Mode | What it runs on |
|---|---|
--local (the default) |
your own database (--db, else DATABASE_URL, else the defaults file's db) |
--demo [PACK] |
a demo store of synthetic data from a data pack (default kart), built on first use (Demo data packs); on every command |
--remote |
the hosted MCP server, in testing: it prints the #aintcare warning (MCP server) |
--demo [PACK] works on every command, not only book: it switches the data source to a demo store and nothing else
(Quickstart, Demo data packs); --demo --remote uses the pack's hosted demo store for the
commands the hosted server runs. model backtest|forecast f1|mtb_dh --remote runs the command as the hosted server's
Lab job (run_job, then get_job until it finishes, its log printed as it arrives; the job's options only), and so
do strategy sweep f1|nascar|motogp, strategy season-strategy f1, model diagnostic f1 and model combo f1, with
the job's knobs as flags (--year 2025, --set NAME=VALUE). racinglines remote tools | call TOOL --ARG VALUE |
job JOB_TYPE reaches any hosted tool, and racinglines data pull SPORT [--since last] copies one sport's shared data
into the local database (MCP: More commands on the hosted server).
When no database is configured, the first command that needs one asks once, local or remote, and saves the answer
as source (Quickstart: First run).
Command groups¶
In the order of racinglines -h. The sport-specific detail (every option of every F1, NASCAR and downhill command)
is in the sections after them: F1 commands, NASCAR commands,
Downhill commands, Road cycling commands.
racinglines data¶
Results, sessions, feeds and market links into the database.
racinglines data -h
racinglines data <command> SPORT [options] (results, sessions, feeds and market links into the database)
fetch f1 nascar motogp cycling Download the sport's source data (results, sessions or feeds) to data/raw/.
download mtb_dh Download Chronorace result files (see: racinglines data download mtb_dh -h).
parse mtb_dh Parse result files into a tidy CSV (see: racinglines data parse mtb_dh -h).
ingest f1 mtb_dh nascar motogp cycling Load the downloaded data into the database.
schedule f1 nascar cycling Store the start times of sessions still to run as scheduled rounds (db/schedule.py), so markets on upcoming sessions have a close.
link nascar Identify the driver, race and contract of stored market links.
events cycling list the event files
startlist cycling fetch an event's start list (Mac only)
probe cycling fetch one PCS page, save it and show what parses (Mac only)
points mtb_dh Championship points tables: import, show, check against official standings.
record f1 Live F1 session recorder: fetch current session via FastF1 and store snapshot to database.
inrace-backfill f1 After a race: the Live tab's in-race win chart from the race's laps, one point per lap, as if the live-timing relay had run all race.
pm-sync f1
pm-links-export f1 Write market links to data/archive/markets/<exchange>/links/ (stable keys).
pm-links-import f1 Load that file into this database (no exchange access needed).
pm-history f1
pm-trades f1
pm-record f1
pm-archive f1
pull any sport One sport's shared data from the hosted server into the local database (racinglines data pull -h).
`racinglines data <command> SPORT -h` for a command's options.
| Sport | Commands | Detail |
|---|---|---|
| F1 | fetch, ingest, schedule, record, inrace-backfill, pm-* (the Polymarket commands, also as racinglines markets) |
F1 commands: Data, racinglines markets |
| Downhill | download, parse, ingest, points |
Downhill commands |
| NASCAR | fetch, ingest, schedule, link |
NASCAR commands |
| MotoGP | fetch, ingest |
NASCAR commands (the same options) |
| Road cycling | fetch, events, startlist, probe |
Road cycling commands |
| any | pull |
below |
racinglines data pull SPORT copies one sport's shared data from the hosted server into the local database
(MCP: More commands on the hosted server).
It needs racinglines.bet, so only its help is shown:
usage: racinglines data pull [-h] [--since SINCE] [--no-markets] [--keep] [--force] [--db DB]
[--mcp-url MCP_URL]
sport
`racinglines data pull SPORT`: one sport's shared data from the hosted server into the local database and data folder
(racinglines/db/pull.py says what a pull holds). Sign-in is `--remote`'s (cli/remote.py: the browser once, or
RACINGLINES_MCP_TOKEN), and the remote-access warning is printed first.
racinglines data pull f1 everything for F1 (the first pull wants a fresh database: db init)
racinglines data pull f1 --since last a top-up from where this database's last F1 pull stopped
racinglines data pull f1 --since 2026-09-01 --no-markets
positional arguments:
sport a sport code (sports/<code>.toml), e.g. f1, nascar, motogp
options:
-h, --help show this help message and exit
--since SINCE 'last' (where this database's last pull stopped) or a UTC date: a top-up
--no-markets race data and model runs only (no exchange history)
--keep keep the downloaded files (data/pulls/<pull id>/)
--force a first pull into a database that already has model runs: replace its race
data and the rows that point at it
--db DB the local database (default: DATABASE_URL or the CLI defaults)
--mcp-url MCP_URL the hosted server (default RACINGLINES_MCP_URL or racinglines.bet)
racinglines model¶
Pricing: forecasts, backtests, model comparisons, one-event diagnostics.
racinglines model -h
racinglines model <command> SPORT [options] (pricing: forecasts, backtests, model comparisons, one-event diagnostics)
forecast f1 mtb_dh nascar motogp cycling Price the upcoming races with the sport's model.
backtest f1 mtb_dh Price past races as of before each one and score them against the results.
walk-forward mtb_dh Price every event through the shared engine (model-only): calibration per market kind.
compare f1 motogp A challenger model against the baseline, race by race.
matrix f1 Every model variant x every trading strategy: accuracy (latest saved backtest per variant, paired vs baseline) and 2026 P&L (latest sweep + season strategy per variant).
diagnostic f1 ONE PAST EVENT at a chosen cutoff (e.g. yesterday); saved as kind='diagnostic'.
combo f1 Combo (same-game parlay) prices for one event (combo_job.py).
props f1 Race props: safety car, red flag, rain, fastest lap (props.py).
sprint-check f1 Walk-forward check of the sprint stage (models/sprint_check.py).
season nascar Cup season forecast (champion odds) from the points standings; read-only.
price cycling price an event's book
reliability cycling calibration check: favourites vs long shots, walk-forward
search motogp Sweep a small set of MotoGP recent-form settings and rank by mean log loss.
No data yet? Add --demo to any command, e.g. `racinglines model backtest kart --demo`: the same command on a synthetic demo store (docs/quickstart.md).
`racinglines model <command> SPORT -h` for a command's options.
A backtest prices every past race as of before it and scores it against the results, uniform and grid baselines; on the kart demo store:
progress 0/1 building history (track features on)
📊 backtest, track features on
progress 10/33 2024 R10 rainbow-road
progress 20/33 2025 R08 mount-wario
progress 30/33 2026 R06 thwomp-ruins
progress 33/33 2026 R09 bowser-s-castle
📊 Track features ON: mean over 32 races
╭────────┬─────────────┬─────────────┬─────────┬────────┬──────────────────┬──────────╮
│ market │ stage │ model Brier │ uniform │ grid │ skill vs uniform │ log loss │
├────────┼─────────────┼─────────────┼─────────┼────────┼──────────────────┼──────────┤
│ win │ pre weekend │ 0.0431 │ 0.0475 │ – │ ▉ +9% │ 0.1518 │
│ │ pre quali │ 0.0405 │ 0.0475 │ – │ █▍ +15% │ 0.1388 │
│ │ pre race │ 0.0381 │ 0.0475 │ 0.0410 │ █▉ +20% │ 0.1266 │
├────────┼─────────────┼─────────────┼─────────┼────────┼──────────────────┼──────────┤
│ podium │ pre weekend │ 0.0988 │ 0.1275 │ – │ ██▎ +23% │ 0.3155 │
│ │ pre quali │ 0.0913 │ 0.1275 │ – │ ██▊ +28% │ 0.3058 │
│ │ pre race │ 0.0772 │ 0.1275 │ 0.0824 │ ███▉ +39% │ 0.2699 │
├────────┼─────────────┼─────────────┼─────────┼────────┼──────────────────┼──────────┤
│ top10 │ pre weekend │ 0.1662 │ 0.2500 │ – │ ███▎ +34% │ 0.5105 │
│ │ pre quali │ 0.1543 │ 0.2500 │ – │ ███▊ +38% │ 0.4897 │
│ │ pre race │ 0.1295 │ 0.2500 │ 0.1345 │ ████▊ +48% │ 0.4353 │
╰────────┴─────────────┴─────────────┴─────────┴────────┴──────────────────┴──────────╯
🏁 favourite won 34% of 32 races · rank correlation 0.83 · mean winner probability 22.7%
best call 2024 R04 Royal Raceway: Donkey Kong won at 48.6%
upset 2024 R08 Mount Wario: Donkey Kong won at 1.7% (favourite Peach)
progress 0/1 building history (track features off)
📊 backtest, track features off
progress 10/33 2024 R10 rainbow-road
progress 20/33 2025 R08 mount-wario
progress 30/33 2026 R06 thwomp-ruins
progress 33/33 2026 R09 bowser-s-castle
📊 Track features OFF: mean over 32 races
╭────────┬─────────────┬─────────────┬─────────┬────────┬──────────────────┬──────────╮
│ market │ stage │ model Brier │ uniform │ grid │ skill vs uniform │ log loss │
├────────┼─────────────┼─────────────┼─────────┼────────┼──────────────────┼──────────┤
│ win │ pre weekend │ 0.0437 │ 0.0475 │ – │ ▊ +8% │ 0.1629 │
│ │ pre quali │ 0.0407 │ 0.0475 │ – │ █▍ +14% │ 0.1391 │
│ │ pre race │ 0.0377 │ 0.0475 │ 0.0410 │ ██ +21% │ 0.1240 │
├────────┼─────────────┼─────────────┼─────────┼────────┼──────────────────┼──────────┤
│ podium │ pre weekend │ 0.1033 │ 0.1275 │ – │ █▉ +19% │ 0.3356 │
│ │ pre quali │ 0.0915 │ 0.1275 │ – │ ██▊ +28% │ 0.3104 │
│ │ pre race │ 0.0768 │ 0.1275 │ 0.0824 │ ███▉ +40% │ 0.2650 │
├────────┼─────────────┼─────────────┼─────────┼────────┼──────────────────┼──────────┤
│ top10 │ pre weekend │ 0.1879 │ 0.2500 │ – │ ██▍ +25% │ 0.5659 │
│ │ pre quali │ 0.1573 │ 0.2500 │ – │ ███▋ +37% │ 0.4944 │
│ │ pre race │ 0.1296 │ 0.2500 │ 0.1345 │ ████▊ +48% │ 0.4326 │
╰────────┴─────────────┴─────────────┴─────────┴────────┴──────────────────┴──────────╯
🏁 favourite won 38% of 32 races · rank correlation 0.83 · mean winner probability 23.1%
best call 2024 R04 Royal Raceway: Donkey Kong won at 52.3%
upset 2024 R08 Mount Wario: Donkey Kong won at 1.6% (favourite Peach)
Per-race rows -> /home/user/racinglines-public/data/demo/kart/runs/f1/backtests/backtest_20261014_0506.csv
A forecast prices the upcoming races and the championships; --save stores the run the web app and the book read:
🔮 forecast 2026 · cutoff 2026-10-14 05:06 UTC · latest results 2026-10-04 14:00
🏁 R10 Rainbow Road Grand Prix (18 Oct)
╭─────────────┬──────────┬───────┬──────────────┬────────┬──────────────┬────────┬───────┬─────────╮
│ driver │ team │ win │ │ podium │ │ top 10 │ DNF │ exp pts │
├─────────────┼──────────┼───────┼──────────────┼────────┼──────────────┼────────┼───────┼─────────┤
│ Mario │ mushroom │ 26.4% │ ███▋ │ 58.7% │ ████████▏ │ 95.0% │ 3.2% │ 15.1 │
│ Luigi │ mushroom │ 18.8% │ ██▋ │ 49.7% │ ██████▉ │ 93.8% │ 3.1% │ 13.4 │
│ Bowser │ koopa │ 14.4% │ ██ │ 40.2% │ █████▋ │ 82.2% │ 13.9% │ 11.2 │
│ Peach │ royal │ 9.9% │ █▍ │ 33.1% │ ████▋ │ 86.7% │ 5.5% │ 10.2 │
│ Daisy │ royal │ 8.3% │ █▏ │ 29.6% │ ████▏ │ 84.4% │ 5.9% │ 9.5 │
│ Donkey Kong │ kong │ 8.0% │ █ │ 27.4% │ ███▊ │ 85.8% │ 3.5% │ 9.3 │
│ Bowser Jr. │ koopa │ 6.7% │ ▉ │ 23.6% │ ███▎ │ 75.2% │ 13.7% │ 8.1 │
│ Diddy Kong │ kong │ 2.8% │ ▍ │ 12.3% │ █▋ │ 72.9% │ 3.3% │ 5.8 │
│ Yoshi │ yoshi │ 1.6% │ ▏ │ 7.7% │ █ │ 60.1% │ 7.3% │ 4.2 │
│ Rosalina │ comet │ 1.2% │ ▏ │ 5.9% │ ▊ │ 55.5% │ 5.6% │ 3.6 │
╰─────────────┴──────────┴───────┴──────────────┴────────┴──────────────┴────────┴───────┴─────────╯
🏁 R11 Wario Stadium Grand Prix (01 Nov)
╭─────────────┬──────────┬───────┬──────────────┬────────┬──────────────┬────────┬───────┬─────────╮
│ driver │ team │ win │ │ podium │ │ top 10 │ DNF │ exp pts │
├─────────────┼──────────┼───────┼──────────────┼────────┼──────────────┼────────┼───────┼─────────┤
│ Mario │ mushroom │ 24.4% │ ███▍ │ 59.3% │ ████████▎ │ 95.4% │ 3.0% │ 15.0 │
│ Luigi │ mushroom │ 20.7% │ ██▉ │ 52.9% │ ███████▍ │ 94.8% │ 3.0% │ 14.0 │
│ Peach │ royal │ 15.6% │ ██▏ │ 44.2% │ ██████▏ │ 90.8% │ 5.9% │ 12.4 │
│ Bowser │ koopa │ 12.9% │ █▊ │ 37.3% │ █████▏ │ 82.2% │ 14.0% │ 10.8 │
│ Daisy │ royal │ 12.1% │ █▋ │ 36.9% │ █████▏ │ 89.1% │ 5.7% │ 11.2 │
│ Bowser Jr. │ koopa │ 4.6% │ ▋ │ 19.1% │ ██▋ │ 75.1% │ 13.7% │ 7.3 │
│ Donkey Kong │ kong │ 4.2% │ ▌ │ 18.4% │ ██▌ │ 82.0% │ 3.1% │ 7.6 │
│ Yoshi │ yoshi │ 2.1% │ ▎ │ 11.0% │ █▌ │ 69.3% │ 7.4% │ 5.4 │
│ Diddy Kong │ kong │ 1.0% │ ▏ │ 6.1% │ ▊ │ 61.1% │ 3.6% │ 4.0 │
│ Birdo │ yoshi │ 0.9% │ ▏ │ 5.3% │ ▋ │ 56.3% │ 7.3% │ 3.6 │
╰─────────────┴──────────┴───────┴──────────────┴────────┴──────────────┴────────┴───────┴─────────╯
🏆 2026 drivers' championship
╭──────────────┬────────┬──────────┬──────────┬────────────────┬────────╮
│ driver │ points │ expected │ champion │ │ top 3 │
├──────────────┼────────┼──────────┼──────────┼────────────────┼────────┤
│ Luigi │ 191 │ 234 │ 99.8% │ █████████████▉ │ 100.0% │
│ Mario │ 134 │ 182 │ 0.2% │ │ 99.1% │
│ Peach │ 106 │ 141 │ 0.0% │ │ 60.5% │
│ Bowser │ 99 │ 135 │ 0.0% │ │ 39.9% │
│ Daisy │ 62 │ 95 │ 0.0% │ │ 0.2% │
│ Donkey Kong │ 57 │ 85 │ 0.0% │ │ 0.2% │
│ Bowser Jr. │ 60 │ 85 │ 0.0% │ │ 0.1% │
│ Koopa Troopa │ 55 │ 58 │ 0.0% │ │ 0.0% │
│ Birdo │ 36 │ 48 │ 0.0% │ │ 0.0% │
│ Diddy Kong │ 28 │ 46 │ 0.0% │ │ 0.0% │
╰──────────────┴────────┴──────────┴──────────┴────────────────┴────────╯
constructors
team points expected champion
────────────────────────────────────────────────────────────────────
Mushroom Racing 325 416 100.0% ██████████████
Royal Racing 168 236 0.0%
Koopa Kart Works 159 220 0.0%
Kong Jungle Racing 85 132 0.0%
Yoshi Island GP 62 91 0.0%
finishing model coef {'g': 0.527, 'g_ease': -0.149, 'rp_rel': 0.041, 'rp_ease': 0.051, 'qp_rel':
0.08, 'street_g': 0.0} sigma 0.155; season drift (as-of) {'team_sd': 0.002652025069557153,
'driver_sd': 0.0005339299639313828}
Saved forecast run 2.
Every option: F1 commands: Pricing and evaluation,
Downhill commands (forecast, backtest, walk-forward), NASCAR commands
(forecast, season).
racinglines model props f1: wet or dry and the DNF check¶
racinglines model props f1 --check --history <csv> runs the walk-forward calibration of the yes/no props on a history CSV
(props.history()'s columns) with no database, scoring the red flag and safety car given wet or dry as well
(climatology, wet_oracle); --dnf-check <csv> scores the position simulation's dnf_prob against the
classification (models/dnf_check.py). Both are read-only; see the decision log of 2026-10-06 in
F1 roadmap.
--forecast <csv> [--lead N] (the leads CSV of racinglines weather leads, e.g.
tests/fixtures/weather/open_meteo-leads-f1.csv, turned into a walk-forward p_wet per race by
weather/wet.py's p_wet_series; lead 5 by default) adds the weather-aware variants to both: climatology-WX to
--check and model-WX to --dnf-check, each with its paired Brier difference against its base. Both read the
race history from --history <csv>, else the database. The forecast's own skill by lead is
racinglines weather backtest. See F1: weather-aware (-WX) variants.
racinglines strategy¶
Trading: replays against exchange tapes, season sweeps, paper signals, strategy searches.
racinglines strategy -h
racinglines strategy <command> SPORT [options] (trading: replays against exchange tapes, season sweeps, paper signals, strategy searches)
replay f1 nascar motogp Replay a strategy against an exchange's recorded prices.
sweep f1 nascar motogp The season sweep: the taker modes and maker variants through each race's weekend.
season-replay nascar motogp Champion markets replayed through the season.
season-strategy f1 Default championship-market strategy through the season: as-of season forecasts pre-season and after every race, trades at Polymarket's recorded prices (+ spread and slippage), settles eliminated markets, marks the rest.
season-checkpoints f1 Championship markets entered at fixed points (pre-season, after 3 and after 6 grands prix) and held, per model variant:
scorecard f1 Pricing scorecard of an exchange weekend (pipelines/scorecard.py): the stored stage runs' fair values vs the result and vs the venue's mid at each stage.
signals f1 nascar motogp Live paper signals of users' strategy profiles (recommendations only).
demo-history f1 nascar motogp Backfill the demo accounts' track record from backtest replays.
reconcile f1 One account's live paper weekend against the backtest's replay of it on the recorded tape (pipelines/reconcile.py; docs/paper-trading.md, rule 1).
profiles f1 List strategy profiles; create A / C / K as Lab candidates if missing.
search f1
search-report f1
search-import f1
search-analyze f1 Analyze saved F1 sweep-search runs and write report artifacts (curves, rankings, charts, noise, HTML; beside search-report).
live mtb_dh Follow a final live from UCI timing: rank probabilities, the maker's quotes, the private book's crowd (pipelines/live_dh.py).
`racinglines strategy <command> SPORT -h` for a command's options.
profiles lists the strategy profiles (A the core taker, C the maker sleeve, K the Kalshi maker, and the alpha's
styles), creating them as Lab candidates when missing:
A candidate #8 A · core taker (update)
C candidate #9 C · maker sleeve (gbm)
K candidate #10 K · Kalshi maker [kalshi]
T1 candidate #11 T1 · update, core (A's settings)
T2 candidate #12 T2 · update, every stage
T3 candidate #13 T3 · update, gridq+pretrain
T4 candidate #14 T4 · update, gbm
T5 candidate #15 T5 · update, 8-pt edges
T6 candidate #16 T6 · update, $200 volume floor
T7 candidate #17 T7 · update, loose podium coherence
T8 candidate #18 T8 · stage-aware (early, $25 volume floor)
T9 candidate #19 T9 · stage-aware, loose podium coherence
T10 candidate #20 T10 · update, baseline model
Every option: F1 commands: Trading research (sweep, season-strategy, season-checkpoints,
replay, search*) and Paper signals and strategy profiles (profiles,
signals, demo-history, reconcile); NASCAR commands (replay, sweep, season-replay,
demo-history, signals); racinglines strategy live mtb_dh.
racinglines backtest¶
The backtest core for any sport with a pricing model (Backtest core).
racinglines backtest -h
usage: racinglines backtest [-h] {walk-forward,coverage,clv} ...
racinglines backtest <command>: the backtest core for any sport with a pricing model (docs/backtest-core.md).
walk-forward SPORT price every event from data before it (racinglines/core/walk_forward.py), model-only:
calibration per market kind; --save stores it as a walk_forward model run (what a
search job runs). The sport's pricing model is named in sports/<code>.toml
([sport] pricing_model), and its settings are its flags.
racinglines backtest walk-forward mtb_dh --seasons 2025 --half-life-days 120 [--save]
racinglines backtest walk-forward motogp --model global --seasons 2026 # the one engine at the results level
racinglines backtest walk-forward nascar --model nascar_recent_form # a challenger ([sport] challengers)
racinglines backtest walk-forward f1 --model global --seasons 2025 2026 --venue polymarket kalshi
# also scores each venue's price beside the model's (model_vs_market): for F1 at the first
# stage, 1 h before any running (--market-stage 'after Quali' for another)
coverage read-only: per sport x exchange x market kind x season, the races linked, settled and taped
(prices, trades, books in Postgres and the Parquet archive) and the parity tier that gives;
season futures; per sport, the race results in the database and the declared results
sources (racinglines/pipelines/coverage.py, docs/parity-rebuild.md "C0")
racinglines backtest coverage [--seasons 2025 2026] [--out DIR]
clv read-only: closing-line value of every paper bet (strategy_signals: the taker's followed
trades, the maker's fills) on any sport and venue, per bet and per sport x venue x kind x
strategy x profile: bets, mean CLV, share positive (racinglines/pipelines/clv.py, "C4")
racinglines backtest clv [--user NAME ...] [--sport nascar] [--venue kalshi] [--event KEY] [--asof UTC] [--summary] [--json]
[--out DIR]
positional arguments:
{walk-forward,coverage,clv}
walk-forward Price every event through the shared engine (model-only).
coverage Read-only: linked, settled and taped races per sport, exchange, kind and
season.
clv Read-only: closing-line value per paper bet, and per sport, venue, kind
and profile.
options:
-h, --help show this help message and exit
racinglines backtest walk-forward¶
The backtest core for any sport with a pricing model (Backtest core):
every event priced from data before it, model-only, calibration per market kind. The sport's settings are
its flags (racinglines backtest walk-forward SPORT -h lists them). What a search job for a sport other
than F1 runs.
racinglines backtest walk-forward SPORT [--db URL] [--seasons 2025 2026] [--kinds K1,K2] [--save]
[--out-dir data/runs/<sport>/walk_forward] [the sport's settings, e.g. --seed N --sims N]
racinglines backtest coverage¶
The coverage counter (parity rebuild package C0), read-only: it writes nothing to the database. It prints four
things. The first is what it counted: the database, the archive trees, and the Postgres and Parquet rows per
store. A header line says when there's no market tape at all, which is how you check a copy such as staging.
The second is one row per sport × exchange × market kind × season of race markets: links, races linked,
races settled, races with a price, trade or book tape (Postgres or the Parquet archive, through
markets/store.py), the parity tier, the maker tier, whether the kind is modeled, and links still unmodeled.
The third is the season futures. The fourth is, per sport, the race results in the database (seasons, events,
events with results) and the results sources its schema declares ([results], [replay] or [model] source,
allowed fallbacks, [sport] results_modules, cleared or not).
A tier counts, per season, the races with both a settlement and a price tape. parity-2 is 8 or more in two
seasons, parity-1 is 8 or more in one, thin is 1 to 7, and missing is none. The maker tier counts
book tape in place of price tape. A kind is modeled when markets/kinds.py defines it, the sport's schema
names a pricing_model, and one of the schema's kinds / *_kinds lists names it. A link's kind is its
prediction, or params.kind while it's unmodeled. Its season is the year of the race's event, or of the
link's end_date for a futures market. --out writes coverage_combos.csv, coverage_futures.csv and
coverage_results.csv.
On the kart demo store (which has links and results but no market tape, so every tape count is 0):
database racinglines_demo_kart · data /home/user/racinglines-public/data/demo/kart · archive trees: /home/user/racinglines-public/data/demo/kart/archive/markets/kalshi, /home/user/racinglines-public/data/demo/kart/archive/markets/polymarket
prices postgres 0 rows · parquet 0 rows in 0 files
trades postgres 0 rows · parquet 0 rows in 0 files
books postgres 0 rows · parquet 0 rows in 0 files
no market tape here: every price/trade/book count below is 0
=== Race markets (tier at 8+ races a season with both a settlement and a price tape; maker_tier needs a book tape) ===
sport exchange kind season links races settled_races price_races trade_races book_races tier maker_tier modeled unmodeled_links
f1 kalshi race_p2 2026 33 3 0 0 0 0 missing missing no 0
f1 kalshi race_podium 2026 49 3 0 0 0 0 missing missing yes 0
f1 kalshi race_win 2026 43 3 0 0 0 0 missing missing yes 0
f1 polymarket race_h2h 2026 30 3 0 0 0 0 missing missing yes 0
f1 polymarket race_podium 2026 49 3 0 0 0 0 missing missing yes 0
f1 polymarket race_pole 2026 17 3 0 0 0 0 missing missing yes 0
f1 polymarket race_win 2026 60 3 0 0 0 0 missing missing yes 0
combos per tier: parity-2 0, parity-1 0, thin 0, missing 7
=== Season futures (no race) ===
none linked
=== Results depth and sources (per sport) ===
sport first_season last_season events events_with_results results_source fallbacks source_modules cleared
mtb_dh <NA> <NA> 0 0 chronorace yes
f1 2024 2026 36 33 f1timing fastf1 yes
nascar <NA> <NA> 0 0 nascar_cf wikipedia (summary_only) nascar yes
motogp <NA> <NA> 0 0 motogp_api motogp yes
indycar <NA> <NA> 0 0 indycar no
road_cycling <NA> <NA> 0 0 procyclingstats pcs yes
le_mans <NA> <NA> 0 0 wec no
sailgp <NA> <NA> 0 0 sailgp no
More results deepen the models' training (walk-forward burn-in); P&L backtests also need market tape, which starts when recording began.
racinglines backtest clv¶
Closing-line value (CLV) per paper bet (parity rebuild package C4; the owner's go/no-go metric for real money is CLV
above +2 % over 100 live bets). Read-only: it computes CLV on read and writes nothing to the database
(racinglines/pipelines/clv.py). A bet is a paper trade in strategy_signals: a taker's buy or sell it followed, or
a maker's paper fill, on any sport (F1, NASCAR, MotoGP) and venue (Polymarket, Kalshi, OG.com). It prints one row
per sport × venue × kind × strategy × profile (bets, decided, undecided, mean CLV in points and percent, share of
decided bets with a positive CLV, mean move against the mid at entry) with an all row, then every bet.
racinglines backtest clv [--db URL] [--user NAME ...] [--sport nascar] [--venue kalshi] [--event KEY] [--asof UTC] [--summary] [--json] [--out DIR]
CLV is the close minus the entry, both as the YES token's price, signed for the side held: positive when the market
closed on our side of the price we paid. In percent it's divided by the price paid for that side. The entry is the
fill price the signal stored. The close is the start of the round that decides the market (markets/kinds.toml:
the kind's session, else a stage_top_n or stage_top stage, such as qualifying for pole; else the race), read
from rounds.extra.session_date or the sport's [sessions] time_key (NASCAR's run_date_utc: the race's start is the
feed's local race time plus the weekend's own UTC offset, from its practice and qualifying runs). A race with no
stored start (a NASCAR weekend whose runs carry no UTC time) closes on race day from
the schema: the event's date plus [replay] race_day_offset days, at [sweep] quote_until_hours UTC (default
00:00). An earlier round with no stored start gets no close. The closing price is the venue's own reading at the close
(markets/venue_replay.py): the book's mid where the venue keeps a quote (Kalshi), else the last tape price within
6 hours. A bet stays undecided, with its reason, when its close is still ahead, it was made after the close, it
has no race (season futures) or no link, or the venue has no price in the 6 hours before the close. --asof judges which markets have closed as of that time (default now). --out writes
clv_bets.csv and clv_summary.csv. A season sweep computes the same CLV for its own trades and saves it with the
run (metrics.clv, below); its in-memory taker trades go through clv.from_trades and its maker fills through
clv.from_fills.
racinglines markets¶
racinglines markets -h
racinglines markets <command>: exchange data (Polymarket and Kalshi; F1 plus the tape-only sports).
sync Sync the exchange's events into market links (live prices, resolutions)
history Store price history for events (--fidelity 1 = minute-level)
trades Store every taker trade for events (the tape a maker replay fills against)
record Record order-book snapshots every --interval seconds (archives to Parquet hourly)
archive Move stale prices / trades / books from Postgres to Parquet (--stats: sizes)
disagree Cross-venue disagreement log: Polymarket vs Kalshi on every outcome listed on both, per tick
(--event 2026-15, a round number, or season [--year]; --start/--end UTC, --step minutes, --no-save)
settle-check Read-only, any --exchange and --sport: our outcome from the results (markets/kinds.settle) beside
the exchange's resolved_yes on every resolved link; lists disagreements, exit 1 if any [--out FILE.csv]
Options: --exchange polymarket (default) --sport f1 (default), then the command's own options.
Polymarket for a tape-only sport (--sport nascar|motogp|indycar; sports/<code>.toml [markets.polymarket] tags,
unverified against the live Gamma API; markets/polymarket/sync.py). Additive: rows are upserted, never deleted:
sync [--year 2026] [--closed] [--tags SLUG …] the sport's Polymarket events into market links
trades [--events SLUG …] the tape of those events' markets
history [--events SLUG …] --start --end [--fidelity 60] price history (minutes per point)
books [--events SLUG …] one order-book snapshot per open market
(--sport f1 keeps the commands above, except `trades` and `history` without --events, which take every F1 market
the same way; `record` is F1 only.) `trades --open --since-hours H` on any exchange: only markets not closed, only
trades from H hours ago on (the recorder's pass, scripts/vm/record_venues.sh and pm_sync.sh; the one-off backfill,
scripts/vm/backfill_tape.sh, takes --since-hours alone).
Kalshi (--exchange kalshi; markets/kalshi/, built on mocked responses, unverified against the live API):
sync [--year 2026] [--closed] [--series TICKER …] the sport's Kalshi markets into market links
trades [--events TICKER …] the tape of those events' markets
history [--events TICKER …] --start --end [--period 60] candlesticks (minutes: 1, 60, 1440): price, bid, ask
[--save-raw DIR | --from-raw DIR] also keep Kalshi's raw responses / re-import them offline
books [--events TICKER …] one order-book snapshot per open market
coverage [--events TICKER …] stored prices with the candle's bid/ask (Parquet + Postgres)
Exchanges defined as schemas (--exchange og; exchanges/<code>.toml, markets/exchange_driver.py), read-only:
sync [--year 2026] the sport's markets and quotes into market links
settle [--since UTC] [--max-pages N] outcomes of closed links from the settlement feed
(resolved_yes; a void is recorded in params and left empty); a schema with no feed does nothing
--check: no scan, read-only: compare our settlement with resolved_yes (= settle-check)
trades [--events SYMBOL …] the tape the exchange still serves (about a month)
history [--events SYMBOL …] --start [--end] minute prices (clipped to what the exchange keeps)
books [--events SYMBOL …] one order-book snapshot per open market
fair the model's fair price beside the quote, net of the taker fee: a simple indicator, no trades
buy-all debug, read-only: one YES and one NO of every market (modeled or not) at the first price stored
(minute price, trade, book side or the sync's quote, whatever the spread or depth), held; P&L settled
when resolved, else marked at the last price stored [--cost 0.01] [--fee F] [--out FILE.csv]
--sport names the sport (default f1): the tape-only sports (nascar, motogp, indycar; sports/<code>.toml
[markets.kalshi] / [markets.polymarket]) are synced only when named, every link unmodeled, under their own
competition. Without --events, trades / history / books take every event of --sport's competition on the
exchange (books: open markets).
Exchange data (Polymarket; F1 is the only sport listed). racinglines markets CMD
is the same as racinglines data pm-CMD f1; group options are --exchange polymarket
and --sport f1 (the defaults) and --db URL.
racinglines markets sync [--year 2026] [--closed] [--alert]
racinglines markets history --events 'f1-azerbaijan-grand-prix%' --start 2026-09-22T00:00 --end 2026-09-26T12:00 [--fidelity 60]
racinglines markets trades --events 'f1-azerbaijan-grand-prix%'
racinglines markets record [--events …] [--interval 60] [--minutes 0] [--sync-every 30] [--no-alerts] [--year 2026]
racinglines markets archive [--stats] [--vacuum-full] [--compact] [--hours H]
racinglines markets disagree --event 2026-15 | --event season [--year 2026] [--start … --end …] [--step 60] [--no-save]
racinglines data pm-links-export f1 [--exchange kalshi]
racinglines data pm-links-import f1 [--exchange kalshi]
| Command | What it does |
|---|---|
sync (f1 pm-sync) |
Sync Polymarket's F1 events of --year into market_links (live prices, resolutions). --closed adds every closed event of that year (e.g. a past season). --alert notifies about markets seen for the first time (market_links.first_seen_at). |
history (f1 pm-history) |
Store price history for events (slugs, or a prefix ending in %) between --start and --end (UTC). --fidelity is minutes per point (default 60; 1 = minute-level). |
trades (f1 pm-trades) |
Store every taker trade for events (the tape the maker replay fills against). |
record (f1 pm-record) |
Record order-book snapshots every --interval seconds (default 60) of the given events, or of the open markets of races not yet run. Runs until stopped (--minutes N stops after N). Re-syncs Polymarket's F1 events every --sync-every minutes (default 30; 0 = never) so new race markets get recorded, and alerts about new markets unless --no-alerts. Archives to Parquet hourly. Run it through race weekends. |
archive (f1 pm-archive) |
Move stale prices, trades and books from Postgres to Parquet under the retention policy, or every row older than --hours H. --vacuum-full returns freed space to the OS, --compact merges each month into one file, --stats only shows where the rows are. See Database. |
disagree |
The cross-venue disagreement log (Kalshi history): for every outcome of a race weekend (--event 2026-15, or a round number of --year) or of the season's drivers' and constructors' champion markets (--event season) that is linked on both Polymarket and Kalshi, one row per --step minutes (default 60) with both mids and tops of book, our fair from the run in force, each venue's fee-adjusted taker edge and the cross-venue gap net of both taker fees, upserted into market_disagreements (--no-save: report only) and printed day by day. The window defaults to where both venues have stored prices, and for a race ends at its start. Reads the archived prices, books and tapes, so it needs no exchange access. |
f1 pm-links-export |
Write market_links to data/archive/markets/polymarket/links/market_links.parquet, with database ids swapped for stable keys (event key and category, FastF1 driver id, competition and category codes). |
f1 pm-links-import |
Load that file into this database (no Polymarket access needed); replaces each token's row and reports rows whose keys don't resolve. --exchange kalshi does the same for Kalshi's links (data/archive/markets/kalshi/links/). |
Kalshi (--exchange kalshi)¶
Built from Kalshi's public API docs, then run against the live read-only API on 2026-09-28 (sync with
--closed, trades, history and books; no order has been sent). Nothing runs unless you call it.
Markets settled before Kalshi's historical cutoff (about two months back) are read from its /historical
endpoints: settled events' markets, their trades and their candlesticks.
racinglines markets --exchange kalshi sync [--year 2026] [--closed] [--series TICKER …]
racinglines markets --exchange kalshi trades --events KXF1RACE-AZEGP26
racinglines markets --exchange kalshi history --events KXF1RACE-AZEGP26 --start 2026-09-24T00:00 --end 2026-09-27T00:00 [--period 60]
racinglines markets --exchange kalshi history --start 2025-01-01 --end 2026-10-05 --save-raw data/raw/kalshi/candles-2025-2026 # + raw responses
racinglines markets --exchange kalshi history --start 2025-01-01 --end 2026-10-05 --from-raw data/raw/kalshi/candles-2025-2026 # re-import, no network
racinglines markets --exchange kalshi books --events KXF1-26
racinglines markets --exchange kalshi --sport nascar sync [--closed] # NASCAR Cup: KXNASCAR* series, links unmodeled
racinglines markets --exchange kalshi --sport nascar trades # every NASCAR event's tape (no --events needed)
racinglines markets --exchange kalshi --sport motogp books # one snapshot per open MotoGP market
racinglines markets --exchange kalshi --sport road_cycling sync --closed # UCI road: KXCYCLING* series, incl. settled grand tours
racinglines markets --sport nascar sync [--tags nascar] # the same sport's Polymarket markets (tape only)
racinglines markets --sport nascar trades # trades / history / books work the same way
| Command | What it does |
|---|---|
sync |
Find Kalshi's F1 series (Sports series whose ticker starts KXF1 or whose title says Formula 1, F1 or Grand Prix, or kalshi.sync.SERIES; --series TICKER … names them instead), and upsert one market_links row per market (exchange='kalshi', token_id = the market ticker's YES contract, condition_id = the event ticker), classified into the model's kinds: win, podium, top 10, pole, top constructor, fastest lap, head-to-head (the race read from the ticker's code, BRIGP26), champions. The sprint winner and sprint pole (KXF1RACESPRINT, KXF1SPRINTPOLE) classify as race_sprint_win / race_sprint_pole and get a model price (F1); RACINGLINES_KALSHI_SPRINTS=0 lists them as unmodeled instead. The sprint's top 5, top 10, fastest lap and top constructor stay unmodeled. Each row keeps Kalshi's resolution rules (params.rules). --closed adds settled events. Races resolve within --year (2026: 15 weekends, 1,847 modeled links on 2026-09-28). |
trades |
Store every trade on those events' markets in market_trades (the taker's side of YES, at the YES price, in contracts). |
history |
Store candlesticks (--period 1, 60 or 1440 minutes) in market_price_history. |
books |
One order-book snapshot per open market in market_book_snapshots (a NO bid at p is a YES ask at 1 − p). |
--sport nascar / motogp / indycar / road_cycling / le_mans / sailgp (before the command) switches the sync to a tape-only sport: Kalshi's
NASCAR Cup (KXNASCAR*: race winners and the champion), MotoGP (KXMOTOGP*) or IndyCar (KXINDYCAR*) series,
filed under the sport's own competition (sports/<code>.toml, no model) with every link unmodeled. Off by
default: without --sport, sync is the F1 sync above and touches nothing else. For any sport, trades,
history and books without --events take every Kalshi event of that sport's competition (books: its open
markets), so recording a series from listing to settlement is sync --closed, trades and books per pass.
On Polymarket (markets --sport nascar|motogp|indycar sync, no --exchange) the same sports are recorded from the Gamma tags in [markets.polymarket] tags; the slugs are unverified against the live API and --tags SLUG … overrides them. The Polymarket path is additive: it upserts by token id and has no reset or delete. The series prefixes come from [markets.kalshi] series in the schema and were not checked against the live
listing (the cloud can't reach Kalshi): sync --series TICKER … syncs exact tickers.
Data.
The recorder unit is not changed. racinglines-recorder (deploy/vm/systemd/racinglines-recorder.service,
scripts/deploy/vm.sh) still runs markets record --interval 60: Polymarket's F1 books, plus the hourly archive
pass that also files Kalshi's rows. There is no Kalshi record. To record NASCAR or MotoGP on the VM, add a
timer of your own next to racinglines-signals.timer, e.g. racinglines-tapes.service with
ExecStart=/bin/sh -c 'for s in nascar motogp; do racinglines markets --exchange kalshi --sport $s sync --closed
&& racinglines markets --exchange kalshi --sport $s trades && racinglines markets --exchange kalshi --sport $s books; done'
(same User, WorkingDirectory and EnvironmentFile as the recorder) and a racinglines-tapes.timer with
OnCalendar=*:0/5 (books every 5 min; trades are deduplicated, so re-pulling them is safe), then
vm.sh deploy installs whatever is in deploy/vm/systemd/. history --period 60 once after settlement fills
the candle series.
With RACINGLINES_DISAGREE=1, markets record also writes one tick of the disagreement log on every pass
(disagree.record), from the latest stored prices and books of every open race listed on both venues and of the
season's champion markets; the Markets page then shows the log's latest tick (Web app).
markets archive (and the recorder's hourly pass) archives Kalshi's rows too, into data/archive/markets/kalshi/
(Data). No record or links export for Kalshi yet. Pulling a season: sync --year Y --closed,
then trades and history --period 60 per modeled event ticker, from each market's open_time to its close_time.
Orders (markets/kalshi/trade.py): post-only limit orders on YES, checked against the book, the 1-cent grid
and KALSHI_MAX_ORDER_USD (default 25), signed with RSA-PSS (KALSHI_API_KEY_ID, KALSHI_PRIVATE_KEY_PATH),
and sent only when KALSHI_TRADING_ENABLED=true; otherwise a dry run that returns the request. No CLI.
OG.com and other schema exchanges (--exchange og)¶
An exchange defined by a file in exchanges/ goes through one generic driver
(markets/exchange_driver.py); the commands and the fair-price indicator are in Exchanges.
racinglines markets --exchange og [--sport f1|nascar|sailgp] sync | trades | history --start UTC [--end UTC] | books | fair
racinglines markets --exchange og [--sport f1|nascar|sailgp] settle [--since UTC] [--max-pages N]
racinglines markets --exchange og [--sport f1|nascar|sailgp] settle --check # read-only: = settle-check
racinglines markets --exchange og --sport f1|nascar|sailgp buy-all [--cost 0.01] [--fee USD] [--out FILE.csv] # debug
settle writes the outcomes of the sport's closed, unresolved links from the exchange's settlement feed
([endpoints.settlements] in its schema; OG.com's get-expired-settlement-price, since its listing drops a settled
instrument): resolved_yes, plus params.settled_at; a void (a 0.50 settlement) is noted in params.settlement and
left unresolved. Bounded (--max-pages, default the schema's 500) and resumed where the last pass stopped; a schema
with no feed does nothing. The VM recorder runs it hourly, every day
(Exchanges).
settle --check, or racinglines markets --exchange polymarket|kalshi|og [--sport S] settle-check [--out FILE.csv] for
any exchange, is read-only: for every closed link of the sport that the exchange resolved, it settles the link from our
stored results (markets/kinds.settle, the one settlement the walk-forward, the replays and the private book use) and
compares it with resolved_yes. It prints counts of agree / disagree / undecided (with the reason: no result yet, a kind
the results don't decide, a cancelled race, a biggest mover without a stored grid) / unmodeled / void, per kind, then
every disagreement, and exits 1 when there is one (markets/settle_check.py).
OG.com is also a replay venue: racinglines strategy replay nascar --venue og (and --venue all, where exchanges/og.toml
lists the sport) and f1 season-strategy --venue og read its stored minute prices, trades and book quotes whatever their
spread or depth, dropping a stored 0.50 as an empty book's midpoint, at the schema's flat fee per contract
($0.02, unverified).
buy-all (read-only) buys one YES and one NO of every market the exchange lists for the sport, modeled or not, at the
first price stored, holds the pair and settles or marks it (Exchanges).
It is an operator's plumbing check, not a result: a pair loses exactly its costs and fees, so the P&L only shows
that each market was found, priced and valued. It is never shown in the web app or in reporting, and it never counts as a
backtest. The same holds for the race replays' --buy-all / RACINGLINES_BUY_ALL=1 mode above.
Alert channels (racinglines/markets/alerts.py, used by new-market alerts and by
signals): a macOS notification, one JSON line per event in
data/runs/alerts/new_markets.jsonl, plus these when set:
| Variable | Meaning |
|---|---|
RACINGLINES_NTFY_TOPIC |
Phone push via ntfy.sh on this topic (sends market titles to ntfy's public server: pick an unguessable name). |
ALERT_WEBHOOK_URL |
JSON POST (Slack and Discord incoming webhooks work). |
RACINGLINES_URL |
Link in notifications (default https://racinglines.bet). |
Running the recorder persistently¶
Polymarket has no historical order-book API, so markets record must run
continuously, and f1 signals runs every few minutes through race weekends. On
macOS both run as LaunchAgents; the plists are in scripts/ (paths inside are
this machine's repo path: edit them for another checkout).
| Agent | Runs | Schedule | Log |
|---|---|---|---|
scripts/bet.racinglines.recorder.plist (bet.racinglines.recorder) |
racinglines markets record --interval 60 |
at login, restarted if it exits (KeepAlive) |
data/runs/logs/record.log |
scripts/bet.racinglines.signals.plist (bet.racinglines.signals) |
racinglines strategy signals f1 |
at login and every 300 s (StartInterval); outside a race weekend each run only notes when signals start |
data/runs/logs/signals.log |
cp scripts/bet.racinglines.recorder.plist ~/Library/LaunchAgents/
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/bet.racinglines.recorder.plist # install + start
launchctl print gui/$(id -u)/bet.racinglines.recorder | grep -E 'state|pid' # status
launchctl bootout gui/$(id -u)/bet.racinglines.recorder # stop + remove
tail -f data/runs/logs/record.log
The same with bet.racinglines.signals.
racinglines book¶
racinglines book -h
usage: racinglines book [-h] [--demo | --remote | --local] [--mcp-url TOP_MCP_URL]
{render,new,map,price,settle,export,view,logout,config} ...
racinglines book <command> FILE [options]: generic sportsbook lines and slips against our model and the prediction
markets (racinglines/books/slips.py, docs/sportsbook/slips.md). CLI only, outside the web app; read-only on the
database.
map resolve every line's legs to a race, athletes and a kind by exact keys; list what stays unmapped
price + the model's fair, the linked exchange price, the book's implied probability, EV against each
settle + each leg's and line's result from the stored classification, and the payout per unit staked
render one HTML page (and --png) of the priced board: hud (on the book's screenshot), card, or agnostic (model
and exchanges only), with stakes from markets/books.toml (docs/sportsbook/render.md)
new a book file draft from loose text ("A over B 1.57", "A vs B 1.57 2.25", "Name 4.35"): --from-text
export the book's staked lines (a line's `stake`, `placed_utc`) priced and settled, as ledger rows:
`--ledger OUT.csv` (racinglines/books/ledger.py, docs/sportsbook/slips.md#ledger-export); refuses an
existing OUT unless --force, never appends
view render a saved --json result of map, price or settle with Rich (no database)
logout forget the browser sign-in that --remote keeps
config show or set the CLI's defaults (racinglines/cli/config.py): `config`, `config KEY VALUE`, `config KEY --unset`
Where the data comes from (map, price, settle, export), three modes; the default is the defaults file's `source`, else local:
--local a local database (--db, else DATABASE_URL, else the defaults file's `db`): you rebuild the
data or run the app and its MCP server yourself
--demo [PACK] a demo store (racinglines/cli/source.py): a separate local database of synthetic data from a
data pack (default kart), built on first use, priced by the same code as --local. FILE
defaults to the pack's book (racinglines/demo/packs/kart/book.toml); `racinglines model
forecast kart --demo --save` first gives it model prices. Rich by default.
`--demo [PACK] --remote` (or the defaults file's source = remote) prices it on the pack's
hosted demo store instead (its [remote] mcp_url; synthetic data only)
--remote the VM's hosted MCP server (racinglines/cli/remote.py): live model runs and exchange prices.
Signs in through the browser the first time (kept in ~/.config/racinglines/mcp-login.json),
or uses RACINGLINES_MCP_TOKEN (an rl_ token) when set. --mcp-url URL, RACINGLINES_MCP_URL or
the defaults file's `mcp_url` point it at your own server (default
https://mcp.racinglines.bet/mcp)
Options (map, price, settle, export):
--json the whole result as JSON (stable keys: docs/sportsbook/slips.md#json-output)
--rich a coloured table (Rich; racinglines/cli/rich_view.py): the default; --plain (any command) or
RACINGLINES_PLAIN=1 prints the plain text
--db URL the database (default DATABASE_URL)
price and export:
--run ID price legs of that run's competition from it (repeatable; default: the app's own choice
per race, markets/venues.pricing_run)
--sims RACE_ID=FILE an OutcomeSims archive (.npz, models/outcomes.save_sims) for a race: legs on that race are
priced jointly from it (repeatable)
price:
--asof UTC price as it stood then (follow a book through a weekend): the newest model run cut off by
then and each exchange's last tape price by then, no bid/ask (local only)
render (FILE = a book file, priced like `price`; or a cycling price output folder or CSV; or leave it out with
--json-in):
--style hud|card|agnostic --image PNG --out PATH.html --png --chrome PATH --json-in PRICED.json
--bank --kelly --cap --min-ev --scale --drop-under override markets/books.toml [stake]
new:
--from-text TXT --venue CODE --event KEY --odds FORMAT --out FILE.toml [--sport CODE] [--currency USD]
positional arguments:
{render,new,map,price,settle,export,view,logout,config}
render An HTML page (and PNG) of the priced board: hud, card or agnostic.
new A book file draft from loose text lines.
map Resolve each line's legs by exact keys.
price Model fair, market price, book implied probability and EV per leg and per
slip.
settle Results and payouts from the stored classification.
export The staked lines, priced and settled, as ledger rows (--ledger OUT.csv).
view Render a saved --json result with Rich (no database).
logout Forget the browser sign-in --remote keeps.
config Show or set the CLI's defaults (source, mcp_url, db).
options:
-h, --help show this help message and exit
--demo as `--demo` after the command
--remote as `--remote` after the command
--local as `--local` after the command
--mcp-url TOP_MCP_URL
as `--mcp-url` after the command
Generic sportsbook lines and slips (singles and parlays across F1, NASCAR and MotoGP) priced against the model and the
linked prediction markets, read-only and CLI only: map FILE (exact-key mapping of every leg to race, athletes and
kind; unmapped legs listed), price FILE [--run ID] [--sims RACE_ID=FILE] (model fair, market price, book implied
probability, EV against model and against market; same-race legs jointly from simulations or flagged
correlated: EV approximate), settle FILE (results and payouts from the stored classification); --json on each. export FILE --ledger
OUT.csv [--force] writes the staked lines (a line's stake, placed_utc), priced and settled, as ledger rows
(Ledger export).
Three modes: --demo [PACK] (a local demo store of synthetic data), --remote (the hosted MCP server; in testing, it prints
the #aintcare warning), --local (the default);
book config sets the default, book logout forgets the --remote sign-in. See Sportsbook slips.
On the kart demo store (racinglines book map|price|settle --demo, the pack's book): Quickstart: Price a
sportsbook's lines shows all three with their output.
racinglines weather¶
racinglines weather -h
usage: racinglines weather [-h] {probe,fetch,show,leads,backtest} ...
racinglines weather <command>: weather forecasts for an event, in the provider-agnostic forecast frame
(racinglines/weather/; docs/weather.md). --provider picks the source: open_meteo (the default, no key) or
wunderground (optional, unverified, a PWS owner's key in $WUNDERGROUND_API_KEY).
probe Fetch the daily and hourly forecasts for --lat/--lon and write the responses untouched (any key left out)
to --out (default data/raw/weather/<provider>/probe-<UTC time>.json); lists every declared field the
response doesn't have (exit 1 when any is missing).
fetch Fetch, parse and save one issue for an event key (data/weather/<event_key>/), the raw response next to
the probe's; print the summary per session. --session race=START/END (UTC, repeatable) labels the
hourly rows inside that window; with a race window (open_meteo) it also saves the race's wet vote
(weather/wet.py: seven global models, wet-<issued>.json), which the live book's race_rain and
race_red_flag props read.
show Load the event's latest saved issue and print the summary per session, the daily rows and the wet vote.
leads For each race in --races (a CSV: race_id, event_key, venue_slug, race_start_utc; default every F1 race in
the local database), the forecasts Open-Meteo issued 0 to 7 days before its window, per global model, to
--out (one row per race, lead and model; open_meteo.LEAD_COLUMNS). Raw responses under
data/raw/weather/open_meteo/leads/. Leads 1-7 exist from about February 2024; lead 0 earlier.
backtest The 5-day (--lead) wet-race forecast against every race since February 2024, walk-forward: rain,
red flag, safety car and DNF count per method, the lead-time table, wet vs dry correlations. Reads the
local database (or --history CSV: weather.wet.races()'s columns) and the leads CSV (--leads; default the
committed fixture tests/fixtures/weather/open_meteo-leads-f1.csv). --out writes the per-race CSV.
Nothing here runs by default, writes to the database, or touches the VM. The event file has no location yet, so
--lat/--lon are given by hand.
positional arguments:
{probe,fetch,show,leads,backtest}
probe fetch once and write the raw response (the Mac, before anything else)
fetch fetch, parse and save one forecast issue for an event
show print the latest saved issue's summary per session
leads past races: the forecasts issued 0-7 days before each (Open-Meteo archive)
backtest the wet-race forecast's walk-forward backtest (rain, red flag, SC, DNF)
options:
-h, --help show this help message and exit
Weather forecasts per event in one provider-agnostic frame: probe --lat --lon [--out], fetch <event_key> --lat
--lon [--session race=START/END], show <event_key>, leads --out <csv> [--races <csv>] (the forecasts issued 0 to 7
days before past races), backtest [--lead 5] [--history <csv>] [--leads <csv>] (the wet-race forecast against rain,
red flag, safety car and DNFs). --provider open_meteo (the default, no key, verified by
the probe of 2026-10-06) or wunderground (optional, unverified, a PWS owner's key). See
Weather forecasts.
| Variable | Default | Meaning |
|---|---|---|
WUNDERGROUND_API_KEY |
– | Weather Underground (The Weather Company API) key, a PWS owner's key; read by weather probe and weather fetch with --provider wunderground only. |
racinglines db¶
racinglines db -h
usage: racinglines db [-h] [--db DB]
{init,seed,stats,export,snapshot-export,snapshot-import,merge-athletes,changes} ...
racinglines db <command>
init Create or upgrade the schema to the latest migration (Alembic), then seed reference data.
seed Upsert sports / leagues / competitions / categories / venues from registry.py.
stats Row counts and coverage per season and category.
export Write the tidy frame (same columns as `racinglines data parse mtb_dh`'s CSV) for a competition to CSV.
snapshot-export The tables the models read, with ids, to data/archive/db/ (Parquet).
snapshot-import Load that snapshot into a fresh database: an exact replica (same ids, same prices).
merge-athletes Merge two athletes that are the same person (e.g. a name change found by UCI ID).
changes The data change log: ingests that changed anything, merges, notes (--add "why" for a note).
Connection: $DATABASE_URL, or --db URL (default: docker-compose.yml's database).
positional arguments:
{init,seed,stats,export,snapshot-export,snapshot-import,merge-athletes,changes}
init Migrate schema to latest and seed reference data.
seed Upsert reference data from registry.py.
stats Row counts and coverage.
export Write the tidy frame for a competition to CSV.
snapshot-export Model tables with ids -> data/archive/db/.
snapshot-import Load data/archive/db/ into a fresh database.
merge-athletes Merge athlete DROP into KEEP (same person), then delete DROP.
changes The data change log, newest first; --add "why" adds a note.
options:
-h, --help show this help message and exit
--db DB Database URL (default: $DATABASE_URL or the docker-compose database).
Database commands. See Database.
racinglines db [--db URL] init # alembic upgrade head + seed reference data
racinglines db [--db URL] seed # re-apply racinglines/db/registry.py
racinglines db [--db URL] stats # table counts + coverage per season and category
racinglines db [--db URL] export [--competition uci_dhi_wc] [--out splits.csv]
racinglines db [--db URL] snapshot-export # model tables with ids -> data/archive/db/
racinglines db [--db URL] snapshot-import [--force]
racinglines db [--db URL] merge-athletes KEEP DROP [--dry-run]
racinglines db [--db URL] changes [--add TEXT] [--sport mtb_dh] [--limit 20]
| Command | What it does |
|---|---|
init |
Create or upgrade the schema to the latest migration (Alembic), then seed reference data. |
seed |
Upsert sports, leagues, competitions, categories and venues from registry.py. |
stats |
Row counts and coverage per season and category. |
export |
The tidy frame for a competition (same columns as mtb_dh parse's CSV) to CSV. |
snapshot-export |
The tables the models read (race data and market links, with their ids) to data/archive/db/<table>.parquet plus manifest.json. Never web-app tables or model runs. |
snapshot-import |
Load that snapshot into a fresh database (schema already at head): an exact replica, same ids, so seeded prices are identical. Refuses a database that already holds model runs unless --force. See Database. |
merge-athletes |
Merge athlete DROP into KEEP (the same person, e.g. a name change that downhill ingest reports once files carry UCI IDs): every table referring to athletes moves to KEEP, then DROP is deleted. Refuses, changing nothing, if a row would collide (both in the same round). --dry-run only reports. It changes the downhill history the model sees, so it's the owner's call. Logged in the data change log. |
changes |
The data change log, newest first: every ingest that changed data, every merge, and notes (--add "why"). |
db stats on the kart demo store (the first --demo command, so it builds the store first):
demo: building the kart pack's store (no database): database racinglines_demo_kart, folder
/home/user/racinglines-public/data/demo/kart. Synthetic data, made once (a few minutes).
🔧 demo store 0/7 creating the database
🔧 demo store 1/7 schema and reference data (alembic upgrade head, seed)
🔧 demo store 2/7 the pack's data files
🔧 demo store generating: 2024 R01 Mario Kart Stadium
🔧 demo store generating: 2025 R01 Mario Kart Stadium
🔧 demo store generating: 2026 R01 Mario Kart Stadium
🔧 demo store generating: files written
🔧 demo store generating: exchange odds 2026-09
🔧 demo store generating: exchange odds 2026-10
🔧 demo store generating: exchange odds 2026-11
🔧 demo store 3/7 racinglines data ingest f1 --years 2024-2026
🔧 demo store 4/7 racinglines model forecast f1 --save --sims 2000
🔧 demo store 5/7 racinglines data pm-links-import f1
🔧 demo store 6/7 racinglines data pm-links-import f1 --exchange kalshi
🔧 demo store 7/7 ready
demo: the kart store is ready (f1 data; `racinglines db stats --demo kart`) Let's-a go! 🍄
╭─ 📦 database racinglines_demo_kart ──────────────────────────────────────────╮
│ 📅 calendar 🏁 results 🔮 model │
│ sports 8 athletes 20 model runs 1 │
│ leagues 8 athlete identifiers 20 race predictions 60 │
│ competitions 8 results 3,300 standings predictions 20 │
│ categories 11 splits 0 │
│ venues 27 source files 33 │
│ events 36 │
│ races 36 │
│ rounds 165 │
╰──────────────────────────────────────────────────────────────────────────────╯
coverage
competition year category events rounds results
───────────────────────────────────────────────────────────
f1_wdc 2024 DRV 12 60 1,200
f1_wdc 2025 DRV 12 60 1,200
f1_wdc 2026 DRV 9 45 900
racinglines live¶
racinglines live -h
usage: racinglines live [-h] {new,step,run,auto,agent,status,report,reprice,settle} ...
racinglines live: launch and run a live private-book event, for any sport (pipelines/live.py).
racinglines live new f1 2026-16 # write live/f1/2026-16.toml from the schedule
racinglines live new mtb_dh 20260925_mtb --final 3 --quali 2,91
racinglines live step live/f1/2026-16.toml # one idempotent update (F1: acts only when an update is due)
racinglines live run live/f1/2026-16.toml # step at the sport's cadence until the event is settled
racinglines live run live/mtb_dh/20260925_mtb.toml # downhill: the poll loop, locked (one loop per final)
racinglines live run live/f1/2026-15.toml --simulate --no-fetch # a rehearsal on a simulated clock
racinglines live run kart/demo-2026-09 --demo --simulate --no-fetch # the kart demo pack's rehearsal (docs/quickstart.md)
racinglines live agent live/f1/2026-16.toml --install # a macOS LaunchAgent: one step every 5 minutes, locked
racinglines live auto # F1: step every event whose window is open (a 5-min timer)
racinglines live status # every event: live / replay / settled, last update, lateness
racinglines live report live/f1/2026-16.toml # the event report (Markdown + charts + PDF)
racinglines live settle live/mtb_dh/20260925_mtb.toml # record a settled event in the database (live_events)
racinglines live reprice live/mtb_dh/20260925_mtb.toml --every 10 # re-price from the logged raw feed
A spec is a path or sport/event (live/<sport>/<event>.toml). A demo experiment: play money, nothing is
traded anywhere.
positional arguments:
{new,step,run,auto,agent,status,report,reprice,settle}
new Write a launch spec from the schedule, with the sport's defaults.
step One idempotent update.
run Step at the sport's cadence until the event is settled (downhill: one
locked poll loop per final).
auto F1: one step of every committed spec whose window is open (from its
opening to a day after its results), so no event has to be started by
hand.
agent Write (and load) a macOS LaunchAgent for a spec: a step every 5 min,
locked, logged.
status Every event: live / replay / settled, last update, next update, lateness.
report The event report: Markdown, charts and a PDF, in the run folder.
reprice Re-price an event from its logged raw feed with this code (downhill);
scores it against what was quoted live.
settle Record a settled event in the database (live_events: dates, the book's
P&L).
options:
-h, --help show this help message and exit
Launch and run a live private-book event for any sport, from a launch spec (live/<sport>/<event>.toml):
new, step, run, agent, status and report. The commands, options and the F1 engine are in
Live events.
racinglines live new f1 2026-16
racinglines live step live/f1/2026-16.toml
racinglines live agent live/f1/2026-16.toml --install
racinglines live status
The kart pack's rehearsal (racinglines live run kart/demo-2026-09 --demo --simulate --no-fetch) and its report are
run step by step in the Quickstart; afterwards live status lists
the settled event:
racinglines web¶
The web app. See Web app & trading.
ADMIN_USERNAME=admin ADMIN_PASSWORD=... racinglines web # http://127.0.0.1:8000
cloudflared tunnel --no-autoupdate --url http://127.0.0.1:8000 # optional public HTTPS URL
| Variable | Default | Meaning |
|---|---|---|
ADMIN_USERNAME / ADMIN_PASSWORD |
admin / random, printed at start |
Login for every page (sign-in page or HTTP Basic). |
APP_SECRET |
random per start | Signs session cookies and CSRF tokens. Set it to keep sessions across restarts. |
WEB_HOST / WEB_PORT |
127.0.0.1 / 8000 |
Listen address. |
WEB_RELOAD |
off | 1 = auto-reload on code changes (development). |
DATABASE_URL |
local DB on port 5433 | Database connection. |
POLYMARKET_* |
– | Exchange credentials and limits; see Web app & trading. |
racinglines users¶
Web-app accounts and the fantasy-bucks ledger from the command line (racinglines/web/accounts.py). Passwords are
never read back: they are stored as scrypt hashes, and add and reset-password print a one-time password exactly
once.
usage: racinglines users [-h]
{setup,list,add,reset-fantasy,season-demo,reset-password,remove,deactivate,activate} ...
racinglines users -> web-app accounts and the fantasy-bucks ledger from the command line (web/accounts.py).
racinglines users setup the accounts schema, ledger and manual-trading tables
(idempotent; fantasy/ledger.py); every active account
without its 1,000 signup grant gets it
racinglines users list accounts, role, active, fantasy bucks
racinglines users add NAME --role basic|pro|admin a new account; prints a one-time password
racinglines users reset-password NAME a new one-time password, printed once
racinglines users remove NAME delete an account with no markets or bets (else deactivate)
racinglines users deactivate NAME | activate NAME
racinglines users reset-fantasy [--keep maker,taker] [--apply]
every active account but --keep back to one 1,000 grant and no
trading history (paper positions, signals, house bets) and no
strategy profile; counts
only without --apply (on the VM: scripts/vm/reset_fantasy.sh)
racinglines users season-demo [--seed N] [--year 2026] [--apply]
every fantasy player follows a random alpha style at a random
weekend share and follow rate through the season so far, replayed
on the real tapes (fantasy/season_demo.py); a database whose name
contains "demo" only; the draw only without --apply (on the VM:
scripts/vm/season_demo.sh)
Passwords are never read back: they are stored as scrypt hashes, and a reset prints a fresh one exactly once.
positional arguments:
{setup,list,add,reset-fantasy,season-demo,reset-password,remove,deactivate,activate}
options:
-h, --help show this help message and exit
racinglines mcp¶
The MCP server: the app's data and simulations for a chat client (Claude Desktop, Claude Code, any MCP client). See MCP server.
racinglines mcp # stdio: the client launches it as a subprocess
racinglines mcp --http # streamable HTTP on 127.0.0.1:8100/mcp; each request needs an account's token
racinglines mcp token admin [--revoke] # issue (printed once) or revoke an account's token; `token` alone lists holders
| Option | Meaning |
|---|---|
--db URL |
Database URL (default $DATABASE_URL, else the local one). |
--http |
Serve streamable HTTP instead of stdio. Every request must carry a bearer token of an active, non-demo account whose role is in RACINGLINES_MCP_ROLES (default admin,pro): one from OAuth sign-in (signed with APP_SECRET; MCP server) or an rl_ token. Pro account records and jobs are scoped to the caller; SQL and audit tools are admin-only. |
--host / --port |
Listen address in --http mode (default 127.0.0.1 / 8100). |
--no-jobs |
Don't run the Lab's job worker in this process: queued jobs wait for the web app's worker. |
usage: racinglines mcp [-h] [--db DB] [--http] [--host HOST] [--port PORT] [--no-jobs]
racinglines mcp the MCP server over stdio (docs/mcp.md)
racinglines mcp --http [--port 8100] streamable HTTP on 127.0.0.1:8100/mcp; each request needs an account's token
racinglines mcp token ACCOUNT [--revoke] issue (print once) or revoke an account's bearer token; `token` alone lists holders
options:
-h, --help show this help message and exit
--db DB database URL (default $DATABASE_URL, else the local one)
--http serve streamable HTTP (per-account bearer tokens) instead of stdio
--host HOST
--port PORT
--no-jobs don't run the Lab's job worker here (leave queued jobs to the web app)
racinglines remote¶
Any tool on the hosted server's MCP server from the command line, signed in as for --remote (in testing: every
call prints the #aintcare warning). tools lists what the account may call, call TOOL --ARG VALUE calls one and
prints its JSON (--csv FILE writes its rows), job JOB_TYPE --KNOB VALUE queues a Lab job and follows its log
(MCP: More commands on the hosted server).
It needs racinglines.bet, so only its help is shown:
racinglines remote <command> (the hosted server's MCP tools, signed in as for --remote)
tools the tools this account may call, with their arguments
call TOOL [--ARG VALUE ...] call one tool and print its JSON; --csv FILE writes its rows instead
e.g. racinglines remote call get_market_history --race-id 812 --series trades
racinglines remote call list_live_events
racinglines remote call replay_maker --run-id 10833 --half-spread 0.03
racinglines remote call fantasy_strategy
racinglines remote call fantasy_follow --code T2 --sizing kelly --kelly 0.5
job JOB_TYPE [--KNOB VALUE] queue a Lab job and follow its log (list_job_types names them; --set NAME=VALUE
for a sweep setting), e.g. racinglines remote job f1_sweep --year 2025
logout forget the stored sign-in
--mcp-url URL the server (default RACINGLINES_MCP_URL, else https://mcp.racinglines.bet/mcp)
Follow a strategy from the terminal. There is no separate verb: remote call reaches the two MCP tools behind
/strategy/pick (MCP, fantasy/follow.py), on your own account, with the page's gates (closed from lights
out until a day after the race, demo accounts refused). Arguments are typed from the server's schema, so --kelly 0.5
and --min-edge 0.03 arrive as numbers and --rolled true as a boolean.
racinglines remote call fantasy_strategy # what you follow, the five styles, the lock
racinglines remote call fantasy_follow --code T2 --sizing kelly --kelly 0.5 # follow Head start at half Kelly
racinglines remote call fantasy_follow --code T1 --min-edge 0.12 # Steady, tweaked (untested, scored live)
racinglines remote call fantasy_follow --code none # stop: nothing trades until you pick again
A refusal prints "ok": false with its reason and the page's message.
racinglines tui¶
The web app's tabs in the terminal over one MCP session: the hosted server, signed in as for --remote (in testing:
the #aintcare warning), or --local for this machine's database through racinglines mcp over stdio. See
Terminal app.
usage: racinglines tui [-h] [--local] [--db DB] [--no-jobs] [--mcp-url MCP_URL] [--user USER]
[--timeout TIMEOUT]
racinglines tui the web app's screens in the terminal, on the hosted server (docs/tui.md)
racinglines tui --local [--db URL] the same on this machine's database: `racinglines mcp` over stdio
Hosted sign-in is `--remote`'s (cli/remote.py): RACINGLINES_MCP_TOKEN, else the browser once, kept in
~/.config/racinglines/mcp-login.json. The sign-in happens here, before the screen opens, so its URL is readable.
RACINGLINES_MCP_URL the hosted server (default https://mcp.racinglines.bet/mcp); --mcp-url overrides it
RACINGLINES_USER the account preselected on the Paper tab; --user overrides it
options:
-h, --help show this help message and exit
--local this machine's database through `racinglines mcp` (stdio)
--db DB with --local: the database URL (default $DATABASE_URL, else the local one)
--no-jobs with --local: don't run the Lab's job worker here (leave queued jobs to the
web app)
--mcp-url MCP_URL the hosted server (default RACINGLINES_MCP_URL or racinglines.bet)
--user USER account preselected on the Paper tab
--timeout TIMEOUT seconds to wait for one tool call (default 90)
racinglines check¶
Quick validation for new setups: the pipelines on synthetic data, one tiny request
to each data source, and the database. --sport f1|mtb_dh (default both),
--offline skips the endpoints, --no-db the database. See Testing.
racinglines check -h
usage: racinglines check [-h] [--sport {f1,mtb_dh}] [--offline] [--no-db]
racinglines check: quick validation for new setups (about a minute, no data downloaded).
code the pipelines on small synthetic data (no network, no database)
endpoints one tiny request to each data source (FastF1, ChronoRace, Polymarket)
database Postgres reachable and migrated (skipped with --no-db)
Options: --sport f1|mtb_dh (default both) --offline (skip endpoints) --no-db
The full regression suite on real data: docs/testing.md.
options:
-h, --help show this help message and exit
--sport {f1,mtb_dh} Default: both.
--offline Skip the data-endpoint checks.
--no-db Skip the database check.
Offline, against the local database:
code (synthetic data)
✓ f1 code load + measurements 0.3s 6 events, 7,485 laps, 360 practice rows
✓ f1 code as-of leakage guard 0.0s sessions only usable after they end
✓ f1 code training history 0.6s 120 training rows
✓ f1 code race pricing 1.2s favourite Driver 01 36%
✓ f1 code backtest 0.6s 6 race x mode rows, mean win Brier 0.032
✓ f1 code season forecast 0.3s leader Driver 01 71%
✓ markets code maker replay 0.0s 172 fills, P&L +72.56
✓ markets code weekend taker 0.0s 46 trades, P&L +300.15
✓ markets code season strategy 0.2s 39 trades, P&L -93.55
✓ mtb_dh code parse results tables 0.1s 1920 rows, 60 riders, rounds ['final', 'practice', 'qual1']
✓ mtb_dh code season model 0.2s sigma 0.0096, 60 riders
✓ mtb_dh code season forecast 0.2s leader 69%
✓ mtb_dh code backtest 0.2s 1 held-out round(s) scored
database
✓ database Postgres 0.2s localhost:5433/racinglines, schema up to date
14/14 checks passed
F1 commands¶
Formula 1. See Formula 1.
Options every F1 command takes (anywhere after f1):
| Option | Meaning |
|---|---|
--db URL |
Database URL (default: $DATABASE_URL, else the local one). |
--variant NAME |
Model variant from racinglines/models/variants.py: baseline (default), grid, gridq, pretrain, gbm, tail, reset, or a combination such as gridq+pretrain+reset. Saved runs record it; the sweep and season strategy reuse only stored pricing from the same variant. The web app's live prices use baseline runs. |
--half-life DAYS |
Recency half-life of the pace models (default inputs.laps.HALF_LIFE_DAYS). |
Data¶
racinglines data fetch f1 [--years 2026,2025,…] [--sessions Q,S,R] [--rounds 6-15] [--sprints-from 2026] [--force]
racinglines data ingest f1 [--years 2020-2026] [--force]
racinglines data schedule f1 [--through 2026-11-30] [--apply --backup FILE] [--undo --backup FILE]
| Command | What it does |
|---|---|
fetch |
Download sessions from the F1 live-timing archive (FastF1) into data/raw/f1/fastf1/<year>/, newest seasons first (default --years 2026 back to 2020). --sessions takes Q,S,R (default) and/or practice FP1,FP2,FP3,SQ; --rounds limits to some rounds. --sprints-from YEAR fetches sprint races from that season on (default 2026). Sessions already on disk are skipped (--force re-downloads). Waits out FastF1's 500 calls/hour limit. Saves each season's schedule (schedule.parquet). |
ingest |
Load data/raw/f1/fastf1/ into the database: events, results, laps, track profiles. Unchanged events are skipped (--force reloads). |
schedule |
Store the UTC starts of sessions still to run (FastF1's event schedule; NASCAR: the stored race list's schedule entries with start_time_utc) as scheduled rounds with no results (racinglines/db/schedule.py), so a market on an upcoming session has a close (pipelines/clv.closes). Events up to --through (default 30 Nov) that are stored and not completed; a round the ingest wrote is kept, a moved session is updated, nothing is guessed. A dry run unless --apply --backup FILE; --undo removes the scheduled rounds. The F1 ingest rebuilds a weekend's rounds as each session ends but keeps the scheduled ones still to come (db/schedule.clear_for_ingest), clearing them once the race is in; on the VM vm.sh schedule reruns it every 6 hours and writes only when a session is added or moved. |
Pricing and evaluation¶
racinglines model backtest f1 [--variant NAME] [--half-life DAYS] [--start-year 2021] [--races N] [--track both|on|off] [--no-track] [--sims 4000] [--out CSV] [--save]
racinglines model compare f1 BASELINE_RUN CHALLENGER_RUN [--reliability]
racinglines model matrix f1 [--variants baseline,grid,gridq,pretrain,gbm,tail,gridq+pretrain] [--year 2026] [--out MD]
racinglines model diagnostic f1 [--variant NAME] [--half-life DAYS] --event 2026-15 --cutoff 2026-09-25T13:30 [--sims 10000] [--no-track] [--calib-power P] [--ridge-team 4 ...] [--save]
racinglines model forecast f1 [--half-life DAYS] [--year 2026] [--sims 10000] [--no-track] [--save [--scenario LABEL]] [--top 10]
racinglines model props f1 EVENT [--run STAGE_RUN] [--prior-n 16] | racinglines model props f1 --check [--from 2022]
racinglines strategy scorecard f1 [--variant NAME] --event 2026-15 [--venue polymarket|kalshi|both] [--model-key KEY] [--out DIR]
racinglines strategy scorecard f1 [--variant NAME] --all --year 2025 [--rounds 1-12] [--venue polymarket|kalshi|both]
| Command | What it does |
|---|---|
backtest |
Walk-forward over races from --start-year, both before qualifying (grid simulated) and after (real grid), with track features on and off (--track on / --track off, or --no-track, for one). Writes one row per race and mode to data/runs/f1/backtests/ (--out); --save stores kind='backtest'. --races N runs only the last N races. |
compare |
Pair two saved backtest runs by race: challenger − baseline ± 2 standard errors per metric and pricing stage. --reliability adds calibration bins and the calibration error (runs saved since 2026-09-27 keep each driver's probabilities). |
matrix |
The model × strategy matrix: for each variant, the latest saved backtest (accuracy, marked where it differs from baseline beyond 2 SE) and the latest saved sweep and season strategy (P&L). Writes data/runs/f1/matrix.md. See Model × strategy matrix. |
diagnostic |
Price one past event as of --cutoff (UTC), with a leakage audit. --save stores kind='diagnostic'. See Market making. It also takes the model settings of a sweep (--ridge-team, --ridge-slope, --driver-prior-n, --half-life-days, --track-features, --practice-prior, --teammate-corr, --finish-rho-scale, --reset-weight, --seed, and the global --variant), applied while it prices; a saved run records them as params.model_settings. The Lab job f1_diagnostic takes them in its settings knob: the Lab's Run form (under "Model settings"), run_job and the TUI's Model tab send them. |
forecast |
Live: cutoff = now; upcoming races and the championships. --save creates scheduled events for upcoming rounds and stores kind='forecast' (the only kind the web app uses for live fair prices). --scenario LABEL saves kind='scenario' instead, ignored by live prices until promoted in the web app's Lab. A save logs one data_changes row (forecast: the events, run id, --variant, sims, code version and data key), as <sport> forecast --save does. |
scorecard |
The pricing scorecard of an exchange weekend, traded or not (Paper trading, rule 2): at every stage cutoff (before any running, after each session), the stored stage runs' fair values for every linked market kind scored against the result and against the venue's mid at that cutoff (Brier and log loss; n markets, and paired where the venue also priced it). --venue both runs Polymarket and Kalshi; --all --year every raced weekend of a season, per weekend and pooled per kind and stage. Nothing is priced or stored: the stages must have been priced by f1 sweep or f1 signals with the same model settings (--variant, or --model-key). Writes data/runs/f1/scorecard/<event>_<venue>.md / .csv / _markets.csv (or <year>_<venue>…). See F1 evaluation. |
props |
Race props for one event from the race history before it: safety car, red flag, rain (per-circuit rates shrunk to the field rate); --run adds fastest-lap prices from a stored stage run's finishing odds. --check scores the yes/no props walk-forward from --from against the field rate and a coin flip. Nothing is stored. See F1 live test. |
Trading research¶
racinglines strategy sweep f1 [--variant NAME] [--year 2026] [--rounds 1-15] [--no-fetch | --fetch-only] [--reprice] [--reliability] [--save] [SETTINGS …]
racinglines strategy season-strategy f1 [--venue {polymarket,kalshi,og}] [--year 2026] [--sims 5000] [--no-fetch] [--reforecast] [--min-edge 0.03] [--stake-per-edge 500] [--max-stake 150] [--capital 1500] [--save]
racinglines strategy season-strategy f1 --paper --venue {polymarket,kalshi} [--after-round N] [--user maker] [--no-fetch] [--save] [SIZING …]
racinglines strategy season-checkpoints f1 [--variants V1,V2] [--year 2026] [--entries 0,3,6] [--window 3] [--sims 5000] [--out MD] [--save]
racinglines strategy replay f1 --runs 11,12,9 [--sweep]
racinglines strategy search f1 QUEUE.toml [--leaderboard]
racinglines strategy search-plan f1 INPUT.json [--budget 0] [--stop-after 0] [--best-score X] [--margin 0] [--dry-run]
racinglines strategy search-report f1 QUEUE.toml
racinglines strategy search-import f1 RESULTS.json
| Command | What it does |
|---|---|
sweep |
Trade every raced weekend of a season: price before any running and after each session, trade Polymarket, settle. Runs four taker modes (update, hold, last, early) and five maker settings side by side. See Market making. Downloads the season's missing Polymarket history and trades first unless --no-fetch; --fetch-only only downloads (e.g. a past season). Stages are reused only when priced with the same model settings from the same data; --reprice prices them again. --reliability also scores calibration: our fair values and Polymarket's prices at every tradeable stage, per market kind and stage (Brier, log loss, ECE) and in reliability bins, written to sweep_<year>_calibration.csv / _reliability.csv (and saved with the run). --save stores kind='sweep'. --venue kalshi (the venue setting) trades Kalshi's markets instead: its exchange='kalshi' links, one market per ticker, its tape per market from data/archive/markets/kalshi/ and the shared tables (nothing is downloaded), and Kalshi's maker fee (Kalshi history). --grid FILE.json sweeps several settings in one process (a JSON list of {"job": id, "settings": {...}}; the search uses it), sharing what doesn't depend on the strategy; each result and saved run is the same as its own sweep. Closing-line value: after the season is traded, every taker mode's trades and every maker variant's fills get their CLV against the venue's price when each market closed, by the backtest clv rule (read from the database, nothing written); the totals print it next to P&L (CLV +1.20% (+0.0042) · 61% positive of 812 decided), the per-bet rows go to sweep_<year>_clv_bets.csv, and --save keeps the aggregate as metrics.clv: by_strategy (bets, decided, undecided, mean CLV in points and percent, share of decided bets with a positive CLV, mean move against the mid at entry), by_stage (per strategy, a row per stage then its all row), by_event (per strategy and event) and undecided (bets per reason). It adds to the run without changing it: no setting, nothing in the settings or model key, every other metric as before. A close that can't be read prints CLV not computed and saves metrics.clv.error; the sweep's own results stand. |
season-strategy |
The championship-market strategy through the season: as-of season forecasts pre-season and after every race, trades at Polymarket's recorded prices (plus spread and slippage), settles eliminated markets, marks the rest. --reforecast recomputes the cached forecasts. --venue kalshi or --venue og replays the same strategy on that exchange's stored tape instead (no fetch; Kalshi costed at its taker fee, OG.com at its flat $0.02 per contract with no volume floor and prices read as its replay venue reads them, an empty book's 0.50 dropped); Polymarket stays the default and its output is unchanged, and --save stores another venue's replay as kind season_venue_replay so no page reads it as Polymarket's. --paper is the championship sleeve (paper only, off unless given): replay through the decision after --after-round (default: the last raced round) on --venue (Polymarket's championship markets, or Kalshi's KXF1 / KXF1CONSTRUCTORS champion markets by ticker, costed at the taker fee) and store that rebalance as the demo maker's (--user) paper positions under venue season:<venue>, event <year>-season, replacing the previous store on that venue; the weekend records of A, C and K never read that venue. --save also stores the rebalance as a run of kind season_sleeve. See Paper trading. |
season-checkpoints |
Championship markets entered at fixed points (pre-season, after 3 and after 6 grands prix; --entries) and held, one $500 book each, per model variant (--variants). Scores each entry over the next 3 GPs (--window) and to date: P&L, how far the market moved toward our fair value, and the share of our edge it closed. Writes data/runs/f1/season_checkpoints.md; --save stores kind season_checkpoints. See Checkpoint entries. |
replay |
Replay a maker quoting Polymarket through an event's diagnostic runs (ids in time order), with both fill rules, against the real trade tape. --sweep adds the half-spread and fill-rule sensitivity table. |
search QUEUE.toml |
Run a queue of season sweeps in parallel ([search] parallel, hours), each saved as a sweep run. The queue file (e.g. sweeps/poc.toml, sweeps/params-4h.toml) is re-read whenever a slot frees, so pending [[job]] entries can be edited while it runs. A job with venue = "kalshi" (or "og") runs sweep --venue kalshi (the strategies on Kalshi's tape) and gets its own baseline on that venue; a job with sport = "nascar" (or "motogp") and kind = "sweep" runs racinglines strategy sweep <sport> the same way (any sport whose schema has a [sweep] table and lists "sweep" in [search] jobs); its id and title carry the venue, and a [[candidate]] may carry it too (e.g. sweeps/kalshi-maker-k.toml). [search] grid = N runs up to N sweeps of the same season and model in one process (sweep --grid: the measurements, stage pricings, markets and maker tape are read once; every saved run is what its own process would save), and history_cache = true keeps rating histories in data/cache/history/; parallel defaults to every core. Writes data/runs/search/<name>/ (leaderboard.md, results.json, state.json, logs). --leaderboard only rewrites the leaderboard from state.json. See Cloud sweeps. |
search-report QUEUE.toml |
Analyse a search's finished sweeps with the checks in Backtest core: labels against the baseline in the target and held-out seasons beyond a noise floor (from seed replicates when the search ran them), same-fidelity baselines, confirmation at more simulations, P&L without the best event and without the best two, a shape label describing the record, not the risk (steady: still up without its best two events in every season; concentrated: up in every season but not without its best two in one; mixed: up in one season, down in another; losing: down in every season), and candidates with stable ids (<strategy>-<settings_key>). An optional [report] table in the queue sets target, holdout, confirm_sims, rank_sims (the simulation count combos are ranked at; default the settings' 4,000), top, noise. Writes stats.csv, ranking.csv, pnl_curves.json, candidates/, candidates.toml and report.md next to state.json. |
search-import RESULTS.json |
Load a search's sweep runs (e.g. from a cloud session) into this database, marked with params.source. |
search-plan INPUT.json |
Code-only F1 next-run plan from a year-labelled snapshot of stored keys and candidate settings. Deduplicate by the unchanged Settings.key, preserve input order and cap at --budget (default 0). Optional completed-score history enables --stop-after K with --best-score X and --margin M. Prints JSON only; no sweep, database connection, file write or Jev call, with or without --dry-run. See Planner input and stop semantics. |
Local auto-sweeper research CLI: .venv/bin/python scripts/research/auto_sweeper.py POOL.json
is dry-run by default. --execute --budget N --jev-budget N --backup PATH opts into serial,
Jev-chosen sweeps with persistent cumulative budgets and an atomic resume journal. The supplied
finite pool uses exact settings identities; missing same-fidelity baselines and confirmations
also count against budget. See Local Jev-guided auto-sweeper
for the score, interruption/restart, retry and backup/rollback rules. This standalone MVP is not a
remote/MCP job or Lab control.
Sweep settings. Every setting of the schema in
racinglines/pipelines/sweep_settings.py is a sweep flag; the same schema drives the
Lab's Edge Finder form, search queues and each saved sweep's params. Defaults are the
baseline. The model variant is the group's --variant.
| Group | Flag | Default | Meaning |
|---|---|---|---|
| Model | --sims |
4000 | Simulations per stage. |
--half-life-days |
120 | Recency half-life of the pace models. | |
--track-features |
true | Match each car's fast-vs-slow sector profile to the track. | |
--practice-prior |
true | Use this weekend's practice laps once they exist. | |
--ridge-team |
2.0 | Team pace shrinkage (events). | |
--ridge-slope |
6.0 | Track-slope shrinkage (events). | |
--driver-prior-n |
3.0 | Races before a driver's gap to the teammate is trusted. | |
--teammate-corr |
true | Teammates share noise. | |
--finish-rho-scale |
1.0 | Teammate finish correlation scale. | |
--reset-weight |
0.25 | With the reset variant: weight of earlier seasons' car data. |
|
--seed |
not set | Monte Carlo seed. Unset = today's fixed seed (42), the same prices and cache keys. Different seeds give independent noise draws, e.g. to check a search's winner isn't one lucky draw. | |
| Timing | --taker-stages |
every stage (pre-weekend … after Quali) |
Stages takers may trade (every taker mode). |
--late-stages |
after FP3,after Quali |
Stages the stage-aware taker skips. | |
| Taker | --min-edge |
0.05 | Min edge to act (probability). |
--min-edge-h2h |
not set | Min edge for head-to-head markets; unset = --min-edge. |
|
--min-edge-by-kind |
not set | Per market kind, e.g. race_h2h=0.05,race_podium=0.08; each overrides --min-edge (and --min-edge-h2h) for its kind. |
|
--stake-per-edge |
250 | Target cost = this × edge ($). | |
--max-stake |
50 | Max stake per market ($). | |
--cost |
0.01 | Cost per share per trade ($). | |
--bankroll |
not set | Bankroll-aware sizing: each taker mode starts with this bankroll, and its stakes scale with its balance after earlier weekends (balance / bankroll, 0 once it's gone). Unset = fixed sizing. |
|
--root-unit |
not set | The root unit system (Dan Abrams on Circles Off ep. 181): a taker's stake in a market is this unit over the square root of the net odds, unit × √(c / (1 − c)) for a contract costing c (price plus cost), instead of --stake-per-edge × edge. One unit at evens, half a unit at 4/1 (c = 0.20), two units at 1/4 (c = 0.80). The edge only decides whether to bet (--min-edge). Still capped at --max-stake, and scales with --bankroll like the linear rule. Not with --kelly. Unset = off. |
|
--bankroll-growth |
linear | With --bankroll: how stakes follow the balance. linear = balance / bankroll; sqrt = its square root (square-root staking: stake = C × √balance × edge, with C = --stake-per-edge / √bankroll), so a bankroll that quadruples doubles the stakes and one down 75% halves them. With --kelly, Kelly sizes on √(balance × bankroll). Unset = linear. |
|
--kelly |
not set | Kelly sizing (needs --bankroll): a taker's stake in a market is this fraction × the Kelly fraction × its current balance, where the Kelly fraction is (q − c) / (1 − c) for a contract costing c (price plus cost, at the touch where there's a quote) that wins with probability q. Still capped at --max-stake × the bankroll scale per market and at --max-deployed per weekend. 0.5 = half Kelly. Unset = linear sizing. |
|
--thin-edge-mult |
not set | Trade a market under the volume floor when the edge is at least this many times the minimum edge and a recorded order book (at most 10 min old) shows size at the touch: buys only, stake capped at that size. Needs recorded books (markets record); without them nothing changes. Unset = thin markets are skipped. |
|
--max-deployed |
not set | Cap on the capital deployed across a weekend's markets ($). Markets are traded in time order; a buy over the cap is cut to fit. Unset = no cap. | |
| Maker | --half-spread |
0.02 | Quote half-spread ($). |
--size |
50 | Shares per quote. | |
--max-pos |
250 | Max inventory per market (shares). | |
--skew |
1.0 | Inventory skew. | |
--max-disagree |
0.15 | Don't quote beyond |fair − market|. | |
--maker-min-volume-24h |
not set | The maker's own per-market filter: min $ traded in the prior 24 h before quoting a market. Unset = the replay's $100 (--min-volume-24h is the takers' filter). |
|
--fill |
through |
through: a trade must cross our price; touch: at our price; queue: at our price once the recorded book's queue ahead of us is served (queue rule). |
|
--info-skew |
2.0 | Info-timed skew (maker_skew, maker_all). |
|
--widen |
1.5 | Widen factor on bad markouts (maker_widen, maker_all). |
|
| Markets | --market-kinds |
race_win,race_podium,race_h2h,race_constructor_top,race_pole |
Market kinds traded. race_top10 (Kalshi's top-10 finishers, a group of ten for the coherence check) can be added; it is not in the default set and only the takers trade it. |
--coherence-tol-by-kind |
not set | kind=tolerance pairs, e.g. race_podium=0.35: how far a multi-outcome group's prices may sum from its target (as a share of it) before its markets are skipped. Unset = 0.25 for every kind. |
|
--min-volume-24h |
50 | Min $ traded in the prior 24 h. | |
--venue |
not set | polymarket (the default) or kalshi: whose links and recorded tape the strategies trade. Unset = Polymarket, and unset or polymarket leaves every settings key as it was. A profile with this setting paper-trades Kalshi in the signal engine. |
Paper signals and strategy profiles¶
racinglines strategy profiles f1 [--assign-demo] [--venue kalshi]
racinglines strategy signals f1 [--profile A|C|ID|NAME] [--user NAME ...] [--event next|ROUND|YEAR-ROUND] [--asof UTC] [--no-fetch] [--no-alert] [--venue polymarket|kalshi|og] [--book OUT.toml]
racinglines strategy demo-history f1 [--reset] [--user maker taker] [--venue kalshi]
racinglines strategy reconcile f1 --event YEAR-ROUND --profile A|C|ID|NAME [--venue polymarket|kalshi] [--user NAME] [--replicates N] [--no-price] [--asof UTC] [--markdown]
| Command | What it does |
|---|---|
profiles |
List the strategy profiles, creating A (core taker), C (maker sleeve) and K (Kalshi maker, Kalshi history) as Lab candidates if missing, and show which users run which. --assign-demo assigns the demo taker A and the demo maker C (users.prefs["strategy_profile"], with the demo bankrolls). --assign-demo --venue kalshi assigns the demo maker K as its Kalshi profile only (users.prefs["strategy_profile_kalshi"], with venue = "kalshi"), leaving the Polymarket profiles alone; nothing reads that key until the signal engine runs Kalshi, so it changes nothing live. racinglines/pipelines/profiles.py. |
signals |
Live paper signals of every user with a profile, for the weekend in progress (racinglines/pipelines/signals.py): refresh FastF1 and Polymarket data (skip with --no-fetch), price each stage whose session is in, run the profile's strategy with the backtest's code, store strategy_signals and paper_positions, and alert (skip with --no-alert). Recommendations only: never places an order. --profile runs one profile instead of each user's own; --user limits the users; --event picks the weekend (default the next). --asof UTC replays at that time and only prints (markets read at each stage's cutoff, like the sweep); with --profile, --venue is the exchange it replays on, and --book OUT.toml writes its entries as a book file for book price|settle|export (racinglines/books/picks.py). A profile whose settings say venue = kalshi trades Kalshi's markets (its links, tape and maker fee; signals and positions tagged kalshi); there is no flag for it, the profile setting is the switch. --venue kalshi|og runs the users' profiles stored for that exchange (users.prefs["strategy_profile_<venue>"]) instead of their Polymarket ones; the default, polymarket, is the 5-minute timer's pass, unchanged. A stage priced live, and an as-of-now pricing between weekends, is logged once in data_changes as live-price (only when a new run is stored). See Paper trading. |
reconcile |
One account's live paper weekend against the backtest's replay of it (Paper trading, rule 1; racinglines/pipelines/reconcile.py): the strategy_signals and paper_positions the signal engine stored for the profile's account (--user, default its demo account) on that weekend and venue, against signals.compute replayed as of a day after the race on the recorded tape with the maker's "through" fill rule. Prints fills, notional, 60-minute markouts (from the same tape, for both sides) and P&L to resolution, live and replay, per stage, with the verdict: fills and markouts within ±25% of the replay's, P&L inside the replay's noise band, the range of the weekend's P&L over seed replicates (the profile's seed plus --replicates more, default 2: seeds 43 and 44, whose stage pricings are cached as diagnostic runs exactly like a sweep's), at least ±31 (taker) / ±71 (maker) wide. --no-price is read-only on the database (cached seeds only). The replay's signals are compared as signals.store keeps them (one row per market, timestamp, action and side: maker fills sharing a second on Kalshi's tape collapse into one, and the output says how many). A weekend whose rows are the backfilled replay is a self-check and must match exactly. --markdown adds the block for the weekend report. Exit 1 when flagged, 2 when the account has no rows for the weekend. Never writes a signal or a position. |
demo-history |
Backfill the demo accounts' track record: every past weekend with Polymarket race markets, replayed with the profile each account ran then, stored as paper signals and positions flagged as backtest replays (racinglines/pipelines/demo_history.py). Idempotent; --reset deletes the backfill first; --user limits it to maker or taker. --venue kalshi replays the maker's profiles on Kalshi's recorded tape instead, with Kalshi's maker fee, stored apart as venue kalshi (Kalshi history); off by default. |
NASCAR commands¶
NASCAR Cup, from the content feeds at cf.nascar.com (no key). See Data.
racinglines data fetch nascar [--years 2026] [--series 1] [--feeds a,b] [--races 5624,5628] [--force] [--dry-run]
racinglines data ingest nascar [--years 2026] [--series 1] [--force] [--no-laps] (--db URL anywhere after the sport)
racinglines data schedule nascar [--through 2026-11-30] [--series 1] [--apply --backup FILE] [--undo --backup FILE]
racinglines data link nascar [--exchange kalshi] [--apply --backup FILE] [--undo FILE]
racinglines strategy replay nascar [--years 2025-2026] [--venue all|kalshi|polymarket|og] [--kinds a,b] [--min-edge X] [--min-volume USD]
[--events KEY,...|latest] [--tape probe|pull] [--require-tradeable] [--buy-all]
[--save --backup FILE] [--undo BATCH]
racinglines strategy replay motogp (the same options)
racinglines strategy sweep nascar [--year 2026] [--rounds 1-8] [--venue polymarket|kalshi|og] [--reliability] [--save] [SETTINGS …]
racinglines strategy sweep motogp (the same options; venues polymarket | kalshi)
RACINGLINES_NASCAR_SEASON=1 racinglines model season nascar [--year 2026] [--sims 4000] [--seed 7] [--stage-noise 5] [--race-noise auto|N|model] [--standings feed|results] [--quotes] [--csv FILE]
RACINGLINES_SEASON_REPLAY=1 racinglines strategy season-replay nascar [--year 2026] [--venue all|kalshi|polymarket|og] [--sims 2000] [--min-edge 0.03] [--stake-per-edge 500] [--max-stake 150] [--capital 1500] [--out DIR]
RACINGLINES_SEASON_REPLAY=1 racinglines strategy season-replay motogp (the same options)
racinglines strategy demo-history nascar --pick data/runs/replay-grid/nascar (print the selection, write nothing)
racinglines strategy demo-history nascar --grid data/runs/replay-grid/nascar --backup FILE [--book kinds|blend|best]
[--venue kalshi|polymarket] [--grid-venue kalshi|polymarket] [--users taker] [--events KEY,...|latest]
racinglines strategy demo-history nascar --reset --backup FILE [--venue kalshi|polymarket] [--users taker] [--grid FOLDER]
racinglines model forecast nascar [--races 3] [--save --backup FILE] [--undo RUN_ID]
racinglines strategy signals nascar [--venue all|polymarket|kalshi|og] [--profile ID|NAME] [--user NAME ...] [--event next|KEY] [--asof UTC] [--no-fetch] [--no-alert]
racinglines strategy demo-history motogp | forecast | signals (the same options)
| Command | What it does |
|---|---|
fetch |
Download the feeds to data/raw/nascar/cf/<year>/<series>/, byte for byte, one request per second, for every race already run. Files on disk are skipped and a feed the server says is missing (HTTP 403) is remembered in a .missing marker (not for races from the last three days, whose feeds may not be published yet), so a re-run after each race weekend asks only for what is new. --feeds picks from race_list_basic, points-feed, weekend-feed, pit-data, loopstats, lap-times, lap-notes; --races limits to some race ids; --force asks again; --dry-run requests nothing and prints how many requests a run would make. |
ingest |
Load the stored feeds into the database: events (races still to run as scheduled), rounds (practice, qualifying, race), results, laps, and one athlete per NASCAR driver_id. A race whose files have not changed is skipped (--force rebuilds it); --no-laps leaves out the lap table. Only Cup (--series 1) has a competition. |
link |
Say which driver, race and contract each stored NASCAR market link is about (Kalshi, Polymarket, OG.com), the same answer the syncs now give new links (Data). A dry run by default: totals, one line per exchange, series and kind with how many links got a driver and a race, and the names it could not place. --apply writes athlete_id, race_id and params (kind, nascar_series, season) of the links that change and refuses without --backup FILE, a database dump under 24 hours old; it records a data_changes entry and an undo file (data/backups/db/nascar-links-undo-<UTC>.json) that --undo FILE replays. --exchange limits it to one exchange. Run it after ingest, since it matches against the drivers and races in the database. |
replay |
Taker replay of the season's Cup races against the recorded prices of every exchange the sport lists (--venue all, the default: Kalshi, then Polymarket, one report each; then OG.com where exchanges/og.toml lists the sport (NASCAR, not MotoGP); --venue kalshi, polymarket or og for one. OG.com's stored minute prices, trades and book quotes all count, whatever their spread, side or depth, except a stored 0.50 from an empty book; its fee is the schema's flat $0.02 per contract, unverified) (pipelines/position_replay.py). Per race: the field is the race's start list, the model in [sport] pricing_model prices it from earlier races only, and the F1 taker (update / hold / last) trades at the stages of sports/<code>.toml [replay] (hours from 00:00 UTC on race day: T-3d, T-1d, race eve) wherever the market is priced, has $50 of 24 h volume, is still open and its group is coherent; the result settles each market through markets/kinds.settle, the walk-forward's settlement (a top-n market: classified by the sport's [results] classified rule and in the top n; NASCAR placed: a retirement counts by its place by laps; MotoGP status_ok). Prints P&L, Kalshi's taker fees and P&L after fees per mode, P&L by kind and model-vs-exchange calibration, and writes data/runs/replay/<sport>-<venue>-<years>/. Kinds: NASCAR race win, top 3/5/10/20 and head-to-head; MotoGP race win. Links are read as link --apply (NASCAR) or the sync (MotoGP, whose [identity] resolver sources/motogp/links.py is on by default since 2026-09-30) left them, and any link not yet identified is identified in memory; nothing is written to market_links. Read-only unless --save, which also stores one as-of model run per race (model_runs kind diagnostic + race_predictions + prediction records and sims, unless RACINGLINES_PREDICTION_RECORDS=0; with the first venue's pass only), refuses without --backup FILE (a dump under 24 hours old), logs a data_changes entry with its batch id, and is undone with --undo BATCH. Each report first counts races with markets, markets priced and markets tradeable; a race whose markets have no stored price at any stage is flagged NO TAPE, and a venue where nothing was tradeable prints NOT TRADED with the reason instead of P&L lines (--require-tradeable then exits 1). The tape (prices and trades) is pulled by --tape pull --backup FILE: for the selected races only, their markets' trades and hourly prices from 2 days before the first stage to 2 days after the last (idempotent upserts, logged in data_changes), then the replay runs; --tape probe asks each exchange about 3 markets of the first selected race, prints the counts, writes nothing and stops. --buy-all (debug, off by default; or RACINGLINES_BUY_ALL=1, which the F1 weekend sweep also reads) adds the mode buy_all (markets/strategies/buy_everything.py): one YES and one NO share of every market open and priced strictly inside (0, 1) at its first such stage, with no edge, volume, price-band or coherence filter, costs and Kalshi's fee still applied, so a pair loses exactly its costs and fees and the cell shows that the market reached the replay and settled; its report adds P&L by side and kind and buy_all_trades.csv, and the taker's modes are unchanged. buy_all is a plumbing check, not a result: it shows that the tape reached the replay and settled, it is never shown in the web app or in any report, no page or status cell counts it as a backtest, and demo-history never stores it. Otherwise the F1 sweep is not involved. |
sweep |
The season sweep (pipelines/season_sweep.py; also motogp sweep): the F1 sweep's four taker modes (update, hold, last, early) and five maker variants (maker, maker_flat, maker_skew, maker_widen, maker_all) through each race's [replay] stages on one exchange's recorded tape (--venue, default Polymarket; Kalshi's taker fee schedule and maker fee, OG.com's flat fee per contract on the taker's cost), settled through markets/kinds.settle as the replay, with the F1 sweep's closing-line value per strategy (printed next to P&L, saved as metrics.clv). The schema says everything sport-specific (sports/<code>.toml [sweep]: stages = "weekend", the default stage mode, which the --stages setting can override with any mode the schema supports, so the weekend-based results stay reproducible beside a session-aware one; the stages the stage-aware taker skips, the hour of race day the maker stops quoting; [replay] stages, kinds and group targets; [sport] pricing_model). The field is the race's start list (its entrants less DNS) and the model prices it once from earlier races only (the fair is the same at every stage; only the market moves). --stages sessions trades each event after each of its stored sessions instead ([sessions] and [stages] in the schema: NASCAR practice and qualifying, MotoGP FP, Practice, Q1/Q2, sprint and warm-up), asking the model at every stage; an event with no stored session time keeps its [replay] stages. --stages race_day (NASCAR) trades the [replay] stages plus [replay] race_day_stages (a 12:00 UTC "race morning" stage, where Polymarket's NASCAR volume is), the maker quoting until [sweep] race_day_quote_until_hours; opt-in, the weekend default is unchanged. nascar replay and nascar demo-history take the same --stages weekend|race_day. The settings are the model's own plus the shared taker, maker and markets ones (every setting is a flag, e.g. --min-edge 0.08 --market-kinds race_win; taker_stages / late_stages take the stage labels). Read-only unless --save, which stores kind='sweep' under the sport's competition with sport and venue in its params; the search queues it as kind = "sweep" with sport = "<code>" (Cloud sweep). |
demo-history |
The replay's trades as the demo accounts' paper portfolio, shown in the app and written only while RACINGLINES_SPORT_PAPER is on, which is the default since 2026-09-30 (unset = on; 0 or any value other than 1/true/yes/on turns it off, and then the command refuses to write; --pick works either way) (also motogp demo-history; Paper trading). Every row is in-sample and labelled so. --grid data/runs/replay-grid/<sport> --backup FILE (the grid is the overnight run's read-only settings grid, runs <year>-e<edge>-v<volume>/<venue>/summary.json): picks each market kind's best grid setting (best worse-season net after fees; in-sample), replays the grid's seasons and stores each race's update trades as backfilled signals and Kalshi paper positions for --users (default taker), replacing that race's earlier rows; --book blend trades every kind at the one best setting; --book best is the best-effort book (best total per kind, else every kind at the best total, win or lose); --events latest for a spot check; --pick FOLDER prints the selection and writes nothing; --reset --backup FILE deletes this sport's demo rows on --venue for --users (with --grid too, it then rebuilds). --venue is kalshi (the default) or polymarket; --grid-venue picks the settings from another venue's grid (Polymarket has none, so --venue polymarket --grid-venue kalshi trades Polymarket's tape with the Kalshi grid's selection). --backup must be a dump under 24 hours old. Writes the selection to demo-selection-<venue>.md in the grid folder and prints the undo command. Never the debug buy_all mode. Logs a data_changes entry. On the VM, vm.sh demo runs it for both sports and both demo accounts (VM deploy). |
forecast |
The next scheduled races priced by the sport's model ([sport] pricing_model, the replay's settings), field from the latest race's entrants (also motogp forecast; Web app). Read-only; --save --backup FILE stores one forecast run (model_runs + race_predictions) that the board, race pages and exchange markets read as live prices; --undo RUN_ID removes it. Logs a data_changes entry. --races N (default 3) is how many scheduled races. A sport with no scheduled race stored (or no completed race to take a field from) says so and stores nothing: that is MotoGP today, whose ingest loads finished events only, so it has no forecast until its calendar (the upcoming events) is ingested. Nothing is tuned: the model settings are the replay's ([replay], seed 7). |
signals |
Live paper signals of every user with a profile for this sport (also motogp signals; pipelines/sport_signals.py), on each exchange the sport's schema lists (--venue all, the default) or one: a user's profile for a sport and venue is users.prefs["strategy_profile_<sport>_<venue>"]. Per race, the schema's [replay] stages: each stage due is priced once by the sport's pricing model on the race's start list and stored (model_runs kind diagnostic, params mode = "live signals", sweep_stage, live), logged once in data_changes as live-price, and reused by every later pass; the venue's tape is refreshed for the last 26 h first (skip with --no-fetch); the profile's taker or maker runs with the sweep's parameters; strategy_signals (detail.sport, detail.venue) and paper_positions (the venue, the sport's event key) are stored and alerted as f1 signals does. --asof UTC replays and only prints (the race priced in memory on its classified field, as the sweep). Recommendations only: never places an order. No timer runs it yet. |
season-replay |
The champion-market replay, read-only and off by default (RACINGLINES_SEASON_REPLAY=1; also motogp season-replay) (Championship markets): the F1 championship sleeve's default strategy traded against each exchange's recorded champion prices (--venue all, the default: every exchange the sport lists, one report each; or kalshi, polymarket, og), with an as-of season forecast after every points race from the results before it only (NASCAR: the Chase model of season, standings summed from results; MotoGP: models/motogp_season.py, Grand Prix and sprint points, Grand Prix only and flagged when no sprint is stored). MotoGP champion contracts are classified in memory; nothing is written to the database. Writes decisions.csv, trades.csv, positions.csv, equity.csv and summary.json to data/runs/season-replay/<sport>-<venue>-<year>/ (or --out DIR). Not yet run on the VM as of 2026-09-30 (Roadmap). |
season |
The Cup season forecast, read-only and off by default (RACINGLINES_NASCAR_SEASON=1) (models/nascar_season.py). The standings today are NASCAR's own points-feed.json when it is on disk (points with penalties, the official seeds; matched by NASCAR driver id), else summed from the season's stored results (the feed's points per race, stage and bonus points included; seeds from regular-season points, ties by wins, then top 5s, then top 10s; --standings results forces this); the output lists every driver whose results-summed points or seed differ from the feed; every points race still to run is priced by [sport] pricing_model for the field of the last race run, turned into points (55 for a win, 35 for 2nd down to 1; 10..1 to each paid stage's top 10, stages simulated as the race order re-drawn with --stage-noise places; the one bonus point a race), each race's finishing order drawn with --race-noise places of spread (auto, the default: the spread measured on the last two seasons, about 7 places, not the race model's own 2.0, which puts most simulated wins on one favourite), and the season runs through the 2026 Chase (top 16 on points, reset 2100 / 2075 / 2065 / 2060 / 2055 then 5 fewer a place to 2000, no eliminations, most points after 10 races). Prints each driver's seed, points today, expected points and the odds of making the Chase, the title, the top 3 and the top 10, and cross-checks the points against NASCAR's own points-feed.json when it is on disk (penalties show up there). --quotes puts the champion price beside every Cup champion contract on file (OG.com's 17, Kalshi's, Polymarket's), with the edge net of the schema fee (OG.com only; Kalshi and Polymarket before fees). Nothing is saved; exchanges/og.toml keeps NASCAR modeled = false, so the board and the syncs do not change. Races are simulated independently (no form drift, no track types), which understates the spread, so the output says NOT CALIBRATED. |
Nothing here runs by default. Back up the database before the first ingest on a real database, and before link --apply (Data changes).
Downhill commands¶
UCI downhill (mtb_dh): ChronoRace result files in, the log-time rider model out (Downhill model).
racinglines data download mtb_dh¶
racinglines/sources/chronorace/download.py.
racinglines data download mtb_dh (--year YEAR | --events SLUG [SLUG ...] | --probe START END)
--discipline {DH,XCO,XCC,EDR}
--category "Elite Men" | "Junior Men" | "Elite Women" | "Junior Women" | ...
[--probe-suffixes SUFFIX ...] [--pdf-results] [--out-dir DIR] [--list-only]
| Option | Meaning |
|---|---|
--year |
Find events from the Wikipedia season page (only slugs dated that year). |
--events |
Explicit ChronoRace slugs; skips Wikipedia. |
--probe |
Also find events by trying YYYYMMDD_{dh,dhi,mtb,xco} for every date from START to END (YYYY-MM-DD) against ChronoRace, for rounds missing from Wikipedia. About 2 s per day. Combines with --year/--events. |
--probe-suffixes |
The suffixes --probe tries (default dh dhi mtb xco). |
--pdf-results |
For rounds without live timing, read the result PDF into the round's table (the 2021 Leogang and Les Gets rounds, several Timed Training rounds). Needs poppler's pdftotext (brew install poppler). Off by default: the round is written as its PDF links. |
--discipline |
DH is the tested one; XC/EDR are best-effort. |
--category |
Friendly name; mapped to ME/MJ/WE/WJ/MU/WU. |
--out-dir |
Default data/raw/mtb_dh/chronorace/. |
--list-only |
Print the discovered slugs and stop. |
Output: <out-dir>/<slug>_<discipline>_<category>.md. Existing files are overwritten.
Typical full refresh:
for y in 2021 2022 2023 2024 2025 2026; do
for c in "Elite Men" "Junior Men"; do
racinglines data download mtb_dh --year $y --discipline DH --category "$c" --out-dir data/raw/mtb_dh/chronorace/
done
done
# 2021 rounds missing from Wikipedia (found by --probe 2021-04-01 2021-10-31):
racinglines data download mtb_dh --events 20210914_dh 20210918_dh --discipline DH --category "Elite Men" --out-dir data/raw/mtb_dh/chronorace/
racinglines data download mtb_dh --events 20210914_dh 20210918_dh --discipline DH --category "Junior Men" --out-dir data/raw/mtb_dh/chronorace/
Add --pdf-results to fill PDF-only rounds from their result PDFs. The 2026 elite files were renamed by
hand (2026-08_les-gets_men-elite.md); a re-download writes <slug>_dhi_elite-men.md beside them, so
rename or remove one of each pair before parsing a directory into a CSV (ingest keys races by event, so
the database isn't affected).
Find unlisted events:
racinglines data download mtb_dh --probe 2021-04-01 2021-10-31 --discipline DH --category "Elite Men" --list-only
racinglines data parse mtb_dh¶
racinglines data parse mtb_dh --inspect FILE
racinglines data parse mtb_dh [--input-dir DIR] [--input-file FILE ...] [--out splits.csv]
[--round LABEL] [--conditions-file CSV] [--canonical-venues]
| Option | Meaning |
|---|---|
--inspect |
Print the detected structure of one file and exit. |
--input-dir |
Parse every .html/.htm/.csv/.json/.md/.txt in the directory (not recursive). |
--input-file |
One file; can be repeated. |
--out |
Output CSV (default splits.csv). |
--round |
Force a round label on every row (rarely needed). |
--conditions-file |
CSV event_id,round,track_condition to fill in track_condition. |
--canonical-venues |
Write venues under their canonical slugs (sports/mtb_dh.toml [venue_aliases], as the database stores them: vallnord → pal-arinsal). Default: as spelled in the source. |
racinglines data ingest mtb_dh¶
racinglines data ingest mtb_dh [PATH ...] [--db URL] [--competition uci_dhi_wc] [--force] [--note WHY]
Loads downloaded files into the database. PATH is files or directories
(non-recursive *.md; default data/raw/mtb_dh/chronorace). Unchanged files are
skipped by content hash, and --force re-ingests them. Each ingested file replaces
its race's rounds, results and splits. See Database. A run that ingests anything
is logged in the data change log, with --note as the reason.
racinglines data points mtb_dh¶
Championship points tables by season, and a rider-by-rider check against official standings (Roadmap → Points validation).
racinglines data points mtb_dh import sports/points/uci_dhi_wc.toml --db # file -> points_schemes
racinglines data points mtb_dh show --db # the tables each season uses
racinglines data points mtb_dh check --db --season 2026 --through-round 7 --standings STANDINGS.toml
| Command | What it does |
|---|---|
import FILE |
Loads one table per era and round kind (final, qual, qual1, semi) into points_schemes, replacing rows with the same era start and round kind. Refuses tables that aren't non-negative and non-increasing. |
show |
Per season: where its tables come from (db or schema), official or placeholder, and their sizes. |
check |
Each rider's cumulative points, from the results and the season's tables, against the official standings (CSV with rider, points, or TOML as in sports/points/standings/). Riders match by name, ignoring accents, case and word order. Exits 1 if any rider differs. --points schema checks the placeholders instead. |
sports/points/uci_dhi_wc.toml is the entry template: every table in it is still a placeholder
(the schema's scales copied into each era). The owner replaces them with the official UCI scales,
sets official = true and cites the source.
racinglines model forecast mtb_dh¶
Predict and backtest one target season.
racinglines model forecast mtb_dh (--db [URL] | --data splits.csv) [--competition uci_dhi_wc] [--save [--scenario LABEL]]
[--out-dir data/runs/mtb_dh/forecast]
[--season YEAR] [--category ME]
[--train-scope {all,season}] [--half-life-days 240] [--junior-weight 0.25]
[--prior-n 0.5] [--eps-df DF]
[--walk-forward] [--backtest 2] [--remaining 2] [--unraced-format {default,last}]
[--sims 10000] [--seed 42] [--top 15]
| Option | Default | Meaning |
|---|---|---|
--db [URL] |
– | Read from the database ($DATABASE_URL or the default if no URL is given). |
--data |
– | Read a tidy CSV from racinglines data parse mtb_dh instead. |
--competition |
uci_dhi_wc |
Competition code in the database. |
--save |
off | Store the model run, its metrics and predictions in the database (needs --db). |
--points |
schema |
Points tables: schema = the placeholders in sports/mtb_dh.toml for every season (today's behaviour); db = each season's tables from points_schemes (mtb_dh points), falling back to the placeholders. |
--scenario LABEL |
– | With --save: store as kind='scenario', ignored by live prices until promoted in the web app's Lab. |
--season |
latest in data | Target season. |
--category |
ME |
Target category. |
--train-scope |
all |
all = every season and category in --data; season = target only. |
--half-life-days |
240 | Recency half-life for training runs (120 before 2026-09-28). |
--junior-weight |
0.25 | Training weight of MJ runs relative to elite (0.5 before 2026-09-28). |
--prior-n |
0.5 | Shrinkage of each rider's pace toward the field (1.5 before 2026-09-28; Calibration). |
--eps-df |
normal | Student-t run noise with these degrees of freedom (above 2), same sd. Tried at 4 and 6: no better. |
--walk-forward |
off | Predict and score every target round from everything before it. |
--backtest N |
2 | Hold out the last N raced rounds; score them and the standings after them. 0 skips. |
--remaining N |
2 | Rounds left in the season, including in-progress events already in the data (forecast with their start lists). The rest are simulated as unknown rounds. 0 skips the forecast. |
--unraced-format |
default |
The format those rounds are simulated in: default = the 2026 elite men's ([rounds] default_format, a 30-rider final) for every category; last = the category's latest completed event's own format (women, juniors), else [rounds.category_format]. |
--sims |
10000 | Monte Carlo simulations (walk-forward uses at most 5000). |
--seed |
42 | Random seed. |
--top |
15 | Rows printed per table. |
Files written: walk_forward.csv, backtest_<venue>.csv, backtest_standings.csv,
forecast_per_round.csv, forecast_standings.csv.
racinglines model backtest mtb_dh¶
Walk-forward plus a standings holdout, for several seasons.
racinglines model backtest mtb_dh (--db [URL] | --data splits.csv) [--competition uci_dhi_wc] [--save]
[--out-dir data/runs/mtb_dh/backtests]
[--seasons 2021 2022 ...] [--category ME]
[--train-scope all] [--half-life-days 240] [--junior-weight 0.25]
[--prior-n 0.5] [--eps-df DF] [--reliability]
[--sims 4000] [--seed 42]
--prior-n and --eps-df work as for forecast. --reliability also prints and saves
(backtest_reliability.csv) reliability curves over every walk-forward round: win, podium,
top 10 and making the Final, per probability bin (Calibration).
--db, --data, --competition, --save and --points work as for forecast (with --points db, each season uses its own tables). With --save, the
per-season and per-event metrics are stored in model_runs.metrics. Seasons with
fewer than 3 events that have data are skipped. Files written:
backtest_events.csv (one row per predicted round) and backtest_seasons.csv (one
row per season). The printed per-season table includes the actual and predicted
champion.
racinglines model walk-forward mtb_dh¶
Every completed event priced through the shared engine, model-only
(Backtest core): calibration per market kind (win, podium,
top 10, fastest qualifier, makes the Final; --kinds race_h2h adds head-to-heads), overall and per season.
racinglines model walk-forward mtb_dh (--db [URL] | --data splits.csv) [--seasons 2025 2026] [--kinds K1,K2]
[--category ME] [--sims 5000] [--half-life-days 240] [--prior-n 0.5] [--junior-weight 0.25]
[--train-scope all] [--eps-df DF] [--seed N] [--save] [--out-dir data/runs/mtb_dh/walk_forward]
Writes walk_forward_events.csv, walk_forward_calibration.csv and walk_forward_reliability.csv.
With --save, stores a walk_forward model run (settings, per-event scores, calibration), which is
what a downhill search job runs.
racinglines strategy live mtb_dh¶
Follow a downhill final live from UCI timing (ChronoRace): rank probabilities after every update, the demo
maker's quotes, the private book's anonymous crowd, and the demo accounts' private-book positions. A demo
experiment; see Live events. The loop holds a lock in the final's run folder (as
racinglines live run does): a second loop for the same final says so and exits instead of polling alongside it.
racinglines strategy live mtb_dh --slug 20260925_mtb --final 3 --quali 2,91 --conditions "clear, rutted"
| Option | Default | |
|---|---|---|
--slug |
(required) | ChronoRace event slug |
--final |
(required) | Live-timing key of the final (Whistler 2026: 3 men, 6 women) |
--quali |
2,91 |
Live-timing keys of the qualifying sessions, comma-separated |
--conditions |
Free text; "rutted" and "wet" widen the spread and raise the crash risk | |
--interval |
20 | Seconds between polls |
--minutes |
0 | Stop after this many minutes (0: when the final is over) |
--once |
One update, then exit |
Road cycling commands¶
Road cycling sportsbook lines (winner, head-to-head) for time trials and road races, from ProCyclingStats results
on the timed-runs engine: events, fetch itt|road, startlist <event>, price <event> [--calibrate]. See
Road cycling.
Scripts¶
| Script | What it does |
|---|---|
scripts/fetch_test_fixtures.py [--f1] [--mtb] |
Build the regression-test fixtures from the public sources. See Testing. |
scripts/signals_parity.py [--profile A] [--year 2026] [--round 15] [--reprice] [--venue kalshi] |
Parity check: f1 signals replayed at a past race's start must give the same taker trades (market, stage, side, shares, price) as f1 sweep --rounds N --no-fetch with the profile's settings; a maker profile (replayed settled, a day after the race) must match the sweep's maker on fills and settled P&L, and demo-history's stored record for the weekend when there is one. --venue kalshi gives the profile the venue = kalshi setting on both sides. Exit 1 on a difference. |
scripts/cloud/prepare.sh |
Before a cloud sweep: move every market row from Postgres to the Parquet archive, db snapshot-export, f1 pm-links-export, and run the no-data-in-git guard. Then commit and push. |
scripts/cloud/start.sh |
Bring up racinglines on a fresh Linux machine (a cloud session): .venv with Python 3.14, Postgres, db init, then db snapshot-import (or, without a snapshot, f1 ingest and pm-links-import / pm-sync), then check --offline. SKIP_SYSTEM=1 runs only the database steps. See Cloud sweeps. |
scripts/hooks/pre-push |
Git hook: README in sync, mkdocs build --strict, no data in git. See Testing. |
scripts/build_readme.py |
See below. |
Docs¶
pip install -r requirements-docs.txt
mkdocs serve # http://127.0.0.1:8000, rebuilds live while you edit
mkdocs build # static site in site/
See requirements-docs.txt for a workaround if watchdog fails to install on Python 3.14.
scripts/build_readme.py¶
Parts of README.md are generated from sections of the docs, so the two can't drift
apart. The docs are the source of truth.
1. Tag a section in any docs/*.md page. HTML comments are invisible in both
MkDocs and GitHub:
Tag names must be unique across all docs pages.
2. Include it in README.md:
Everything between the include markers is replaced on each build, so edit the docs,
not the README. Text outside include blocks (title, section headings, anything
README-only) is left alone. Options go after the name: shift=N demotes headings
inside the section by N levels (negative promotes).
3. Build:
python scripts/build_readme.py # rewrite README.md
python scripts/build_readme.py --check # exit 1 if README.md is stale (the pre-push hook runs it)
python scripts/build_readme.py --list # tagged sections, and whether the README uses each
Conversions while copying:
| In docs | In README |
|---|---|
[x](todo.md#points-validation) |
[x](docs/todo.md#points-validation) |
[x](#some-heading) (same page) |
[x](docs/<page>.md#some-heading) |
!!! warning "Title" + indented body |
> ⚠️ **Title** blockquote |
Sections currently tagged (--list):
| Tag | Page |
|---|---|
tagline, glance, quickstart, overview, results-map, layers, docs-build, repo-layout, points-warning |
index.md |
validation |
testing.md |
f1-accuracy |
f1.md |
f1-trading, f1-matrix, f1-checkpoints |
market-making.md |
headline |
evaluation.md |
forecast-summary |
forecast.md |
todo-summary |
todo.md |