Skip to content

CLI reference

One command, task first: racinglines <group> <command> [SPORT] [options]. Every group, every command, its -h and 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:

Command
racinglines -h
Output
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
Output
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:

Command
racinglines data pull f1 -h
Output
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
Output
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:

Command
racinglines model backtest kart --demo
Output
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:

Command
racinglines model forecast kart --demo --save
Output
🔮 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
Output
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:

Command
racinglines strategy profiles f1 --demo
Output
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
Output
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).

racinglines backtest coverage [--db URL] [--seasons 2025 2026] [--out DIR]

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):

Command
racinglines backtest coverage --demo
Output
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
Output
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
Output
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
Output
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
Output
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):

Command
racinglines db stats --demo
Output
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
Output
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:

Command
racinglines live status --demo
Output
demo-2026-09             f1      settled  last 2026-10-04T17:00 (0.1 h ago)  Bowser's Castle Grand Prix 2026 (synthetic)

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.

Command
racinglines users -h
Output
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.
Command
racinglines mcp -h
Output
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:

Command
racinglines remote -h
Output
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.

Command
racinglines tui -h
Output
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
Output
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:

Command
racinglines check --offline
Output
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:

<!-- readme: quickstart -->
...any markdown...
<!-- /readme -->

Tag names must be unique across all docs pages.

2. Include it in README.md:

<!-- include: quickstart -->
<!-- /include -->

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