Skip to content

Demo data packs

--demo [PACK] on any command runs that command, unchanged, on a demo store: a separate Postgres database and data folder filled from a data pack by the real pipeline.

The commands to run are in the Quickstart. This page is how a demo store works, what the bundled kart pack holds, how the hosted demo store serves it, and how to add a pack of your own.

No demo branch anywhere

There is no demo code path in any command, model or pipeline: the backtest is pricing.backtest, the forecast pricing.forecast, the live book pipelines/live_f1.py with the real crowd and quoting, the book books/slips.py. --demo only changes where the data comes from.

What --demo does

One resolver (racinglines/cli/source.py, called by racinglines/cli/__init__.py before anything else) takes --demo [PACK] out of the arguments and points the process at the pack's store:

Setting Demo value Default location
DATABASE_URL the pack's database, on the same server as the configured one racinglines_demo_<pack> (RACINGLINES_DEMO_DATABASE_URL overrides)
RACINGLINES_DATA the pack's data folder: raw files, archives, run outputs, live specs data/demo/<pack>/ (gitignored; RACINGLINES_DEMO_DATA overrides)
RACINGLINES_OFFLINE=1 FastF1's servers are never called: the store's files are the record (a general switch, also for machines without FastF1 access)

Then the command runs as it always does. PACK defaults to kart; --demo=PACK works too. The first --demo command builds the store when it is missing or when the pack has changed (a Rich progress bar; about a minute). The store build is logged in the store's own data change log, as any ingest is:

Command
racinglines db changes --demo
Output
2026-10-14 05:06  f1      forecast        f1 forecast run 2: 2026-10, 2026-11, 2026-12, baseline, 10000 sims  (root@vm)
2026-10-14 05:05  f1      demo            demo store built from pack kart (synthetic f1 data)  (root@vm)
2026-10-14 05:05  f1      forecast        f1 forecast run 1: 2026-10, 2026-11, 2026-12, baseline, 2000 sims  (root@vm)

A pack can name its sport differently from the schema it is stored under, so its commands read naturally: the kart pack's sport_name = "kart" is why racinglines model forecast kart --demo is the F1 forecast, with F1's options:

Command
racinglines model forecast kart --demo -h
Output
usage: racinglines model forecast f1 [-h] [--year YEAR] [--sims SIMS] [--save] [--top TOP]
                                     [--no-track] [--scenario LABEL]

options:
  -h, --help        show this help message and exit
  --year YEAR
  --sims SIMS
  --save
  --top TOP
  --no-track
  --scenario LABEL  Save as kind='scenario' (not used for live prices until promoted in the web
                    app).

What a pack is

A folder with a pack.toml manifest, bundled under racinglines/demo/packs/<name>/ or in a folder named by RACINGLINES_DEMO_PACKS (several, separated by :). The manifest holds data and the names of real commands only:

Key What
name, title, about the pack's name (what --demo NAME takes) and description
sport an existing sport schema (sports/<sport>.toml) the data is stored under: no new sport, no new schema
sport_name optional: what the quickstart's commands call the sport (kart, read as f1)
files a folder copied as is into the data folder (e.g. raw/... files a real ingest reads, live specs under live/)
[generator] optional: entry = "module:function" writing more data files into the data folder, and its input
[load] commands the real racinglines ... commands that load the data into the database, in order
[book] file the sportsbook book racinglines book price --demo prices when no FILE is given
[remote] mcp_url the pack's hosted demo server, for --demo --remote (below)
tagline optional: a line printed once the store is ready (a pack's flavour lives here, never in a command)
[[quickstart]] the pack's quickstart: title, run (the command), about, optional section (steps group under one heading per section, in first-seen order; none: one list) and record = false for a step that is shown but never run; rendered into the Quickstart by scripts/build_quickstart.py (--commands PACK [--section NAME] lists them) and replayed by the web app's /docs/quickstart/term page (Pack.sections() in racinglines/demo/store.py)

The store build (racinglines/demo/store.py), in order:

  1. create the database (its name must contain demo), Alembic upgrade head and the seed (racinglines db init);
  2. copy the files;
  3. run the generator;
  4. run each load command as a subprocess on the store;
  5. record a data_changes line and write a marker (demo-store.json, a fingerprint of the pack and its generator).

Adding a pack is adding a folder: no command, flag, schema, sport file or code path changes. tests/test_demo_store.py builds a second, tiny pack under mtb_dh (synthetic ChronoRace result files loaded by racinglines data ingest mtb_dh) and runs its backtest through the same code.

The kart pack

racinglines/demo/packs/kart/: a synthetic Formula 1 world with a Mario Kart grid, stored under the F1 schema.

  • world.toml, the generator's input: three seasons (2024-2026, twelve rounds each, 2026's rounds 1-9 raced, sprints at rounds 3, 6 and 10), 20 drivers in 10 teams (Mario, Luigi, Peach, Bowser, ...), twelve tracks (Mario Kart 8 Deluxe's Mario Kart Stadium, Rainbow Road, Bowser's Castle, ...), each team's car pace and the driver offsets, the tracks' lap times (5 % off each course's fastest 150cc world-record lap, from mkwrs.com, so no generated lap beats a record), sector shares, trap speeds, how hard passing is and how often a safety car comes out. Sector speeds and passing come from course profiles where there is one, and how much each team gains on fast or tight tracks from its drivers' in-game stats. These are the synthetic world's hidden truth; the real model never reads them, it fits the generated laps. --demo kart-classic is the first kart world, with made-up F1-length lap times.
  • racinglines/demo/mk_demo/generate.py writes FastF1-shaped files for every raced session (practice, sprint qualifying, sprint, qualifying, race: results, laps with sectors and speed traps, meta with weather) and each season's schedule, the shapes sources/fastf1/fetch.py saves; and quotes from a synthetic Polymarket (win, podium, pole, teammates' head-to-heads) and Kalshi (win, podium, 2nd place) on rounds 9-11, in the file racinglines data pm-links-export writes. Seeded: the same input gives the same files.
  • The load: data ingest f1 (the real FastF1 ingest: events, results, laps, track profiles), model forecast f1 --save (files the unraced rounds as scheduled races), data pm-links-import f1 for each exchange.
  • files/live/f1/demo-2026-09.toml: the live rehearsal's launch spec (round 9, already raced, so the book settles): [live.crowd] takers = 100.
  • book.toml: a generic sportsbook's lines on rounds 10 and 11, one per F1 pricing feature of book price.

The synthetic exchanges are data like the rest: prices drawn around the generator's own odds with an exchange's noise and spread. No real exchange data is in any pack. What the store holds once built, by table:

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

The hosted demo store

Remote access is in testing

racinglines.bet remote access is in testing (#aintcare mode). A review of venue and data-feed terms is pending, so data served here is for testing only. Every command that connects to the hosted server prints this warning on stderr (Quickstart: Hosted).

--demo [PACK] --remote, or --demo with source = "remote" in ~/.config/racinglines/cli.toml, uses the pack's hosted store instead of a local one. The resolver builds nothing locally: it points the command's own --remote path at the pack's demo MCP server ([remote] mcp_url in pack.toml, RACINGLINES_DEMO_MCP_URL overrides it) and adds --remote. Only the commands the hosted server runs take it:

Command How it runs on the hosted store
book map \| price \| settle the pack's book sent to the map_book / price_book / settle_book tools
model backtest \| forecast f1 \| mtb_dh the command's Lab job, queued with run_job and followed with get_job (racinglines/cli/remote.py); the job's options only
db stats, live run, live report not available: these need a local store

model backtest f1 --remote without --demo runs the same way on whichever server --remote names.

The server is the normal one with --demo: racinglines mcp --http --port 8766 --demo kart on the VM serves the kart store (its own database racinglines_demo_kart and data/demo/kart/, built by the same store.build), and its job worker runs the jobs on it. Accounts, rl_ tokens and OAuth sign-ins are read from production's database (RACINGLINES_MCP_ACCOUNTS_URL), so the same Pro and admin accounts sign in, and the store has no account rows of its own: jobs are filed without an owner, so every signed-in caller sees the store's jobs and can cancel a queued one (acceptable on a synthetic store). Account-scoped reads (overview's users, track_record, list_positions, list_signals, our maker's quotes in list_markets) show nothing to a non-admin caller there, because a caller's id belongs to production's users table, not the store's. scripts/vm/demo_store.sh builds it (build, backup first) and starts the server (serve); the owner runs both and adds the mcp-demo hostname to the tunnel.

Add a pack of your own

Step 1: Make the folder

A folder named after the pack, bundled under racinglines/demo/packs/<name>/ or anywhere on RACINGLINES_DEMO_PACKS, with a pack.toml naming an existing sport schema.

Step 2: Bring the data

Either files a real ingest reads (files), a generator that writes them ([generator]), or both. The kart pack's world.toml and generate.py are the worked example; the tiny mtb_dh pack in tests/test_demo_store.py is the smallest one.

Step 3: Name the load commands

[load] commands, in order: the same racinglines data ingest ... and racinglines model forecast ... --save a real database gets.

Step 4: Write the quickstart

[[quickstart]] entries, one per command. Then python3 scripts/build_quickstart.py renders them into the docs, and python scripts/docs/capture_examples.py (after adding them to scripts/docs/examples.toml) captures their real output for the page.

Step 5: Run it

# LOCAL (Mac), or any machine with Postgres
racinglines db stats --demo <name>          # builds the store, then prints what it holds

Next steps

  • Quickstart


    The kart pack's commands with their real outputs, local or hosted.

    Quickstart

  • MCP server


    The hosted server the --remote commands reach, and how to connect a chat client to it.

    MCP server

  • Sportsbook slips


    The book file book map | price | settle reads, and what the EV columns mean.

    Sportsbook slips