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:
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:
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:
- create the database (its name must contain
demo), Alembic upgrade head and the seed (racinglines db init); - copy the files;
- run the generator;
- run each load command as a subprocess on the store;
- record a
data_changesline 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-classicis the first kart world, with made-up F1-length lap times.racinglines/demo/mk_demo/generate.pywrites 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 shapessources/fastf1/fetch.pysaves; 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 fileracinglines data pm-links-exportwrites. 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 f1for 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 ofbook 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:
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.
-
MCP server
The hosted server the
--remotecommands reach, and how to connect a chat client to it. -
Sportsbook slips
The book file
book map | price | settlereads, and what the EV columns mean.