Simulators
Live feed settings and Compose
Every live-feed flag has an environment variable, so a container or orchestrator sets the whole live feed with no argv. Boot a detached instance in Docker, feed it and read its feeder’s log:
rystic run kalshi-twin --live --runner docker -d --name live
rystic feed kalshi-twin --instance live
rystic logs --feed live
--runner docker runs the container with a feeder on this host beside it, and rystic feed --instance feeds a detached instance by name.
Flags and environment
| Flag | Env | Default | Meaning |
|---|---|---|---|
--live | RYSTIC_LIVE=1 | off | feed the simulator from the exchange |
--live-carry N | RYSTIC_LIVE_CARRY | 0 | pre-carry the busiest N markets; 0 carries nothing until the state UI picks; ignored when markets or events are named |
--live-markets a,b | RYSTIC_LIVE_MARKETS | none | carry exactly these markets before anything is picked; the state UI can change the list later |
--live-markets-file path | RYSTIC_LIVE_MARKETS_FILE | none | read the startup watch list from a shared file; an alternative to --live-markets |
--live-events a,b | RYSTIC_LIVE_EVENTS | none | carry every open market of these events, looked up again on every rescan; adds to --live-markets |
--live-events-file path | RYSTIC_LIVE_EVENTS_FILE | none | read the startup event list from a shared file; an alternative to --live-events |
--live-series a,b | RYSTIC_LIVE_SERIES | * | series to carry; * offers every open market on the exchange |
--live-max-markets N | RYSTIC_LIVE_MAX_MARKETS | 500 | how many of the busiest open markets the state UI offers |
--live-shards N | RYSTIC_LIVE_SHARDS | 3 | exchange sockets the carried markets spread over, at most 5 |
--live-env | RYSTIC_LIVE_ENV | the simulator’s default (prod) | exchange environment; demo needs your own demo key |
Flag beats environment beats default. Every --live-x-y reads RYSTIC_LIVE_X_Y. A --live-* flag or live_* stack key without live mode is refused. So is a RYSTIC_LIVE_* variable, unless the simulator declares no live feed. --live=false (RYSTIC_LIVE=0, live: false) turns live off on purpose and ignores them.
A RYSTIC_LIVE_* name that is no live setting is refused, naming the setting it’s closest to. RYSTIC_LIVE_EVENT_FILE, for example, gets “did you mean RYSTIC_LIVE_EVENTS_FILE?”. run, up and feed warn about any variable a typo away from a Rystic one.
- The exchange socket lives in the CLI process, under
--runner dockertoo, where the feeder runs on the host beside the container. --live-shardsplus the sockets of any recorder running on the same key must stay at or under Kalshi’s limit of five.stopends the feeder first, then the simulator.restartre-runs the recorded spec, feed included.
In a stack file
services:
c: {product: kalshi-twin, port: 0, live: true, live_markets: "KXBTC15M-26SEP1712-T1, KXBTC15M-26SEP1712-T2"}
live: true takes every --live-* default, environment included. Each --live-* flag in the table above has a stack key, its name with underscores for dashes, such as live_markets for --live-markets. An explicit empty string clears an inherited list. tapes, tape and copies are the replay keys. Compose export preserves explicit live settings and mounts live_markets_file and live_events_file.
In Compose
Keep the default rystic run command and the credential the export wrote, and add variables under the service’s environment. Quote numeric and boolean values:
environment:
RYSTIC_TOKEN: ${RYSTIC_TOKEN}
RYSTIC_LIVE: "1"
RYSTIC_LIVE_SERIES: KXBTC15M
RYSTIC_LIVE_SHARDS: "2"
RYSTIC_LIVE_CARRY: "1"
The CLI and its feeder run inside this container, so no host feeder or Docker socket mount is needed. command: ["rystic", "run", "--live=false"] overrides RYSTIC_LIVE: "1" for a replay or scenario run. The health check confirms the HTTP server is available. For live data, wait for the selected markets and their books as well, since discovery and exchange subscription happen after HTTP startup. RYSTIC_LIVE_SHARDS is capped at 5, because Kalshi permits at most 5 sockets per key across all clients.
To use your own key, put key_id (the key id, one line) and kalshi.pem next to compose.yaml and add Compose secrets, with no key variables needed. A token file holding your rys_… API key, as the rystic_token secret, replaces RYSTIC_TOKEN the same way. So a shared compose.yaml holds no per-person value:
services:
kalshi-twin:
secrets: [rystic_token, kalshi_key_id, kalshi_private_key]
secrets:
rystic_token: {file: ./token}
kalshi_key_id: {file: ./key_id}
kalshi_private_key: {file: ./kalshi.pem}
rystic doctor kalshi-twin then reports live feed … (from /run/secrets). The container runs as uid 10001. Compose mounts a file secret with the host file’s owner and mode, and ignores uid and mode on one. So a 0600 file fails with permission denied and the uid that needs it. On a Linux host, grant that uid read first, rather than making the key readable by every user:
setfacl -m u:10001:r token key_id kalshi.pem, from theaclpackage- or
sudo chown 10001 token key_id kalshi.pem
KALSHI_KEY_ID holds the id itself, and a path there is refused and pointed at KALSHI_KEY_ID_FILE. RYSTIC_TOKEN_FILE, KALSHI_KEY_ID_FILE and KALSHI_PRIVATE_KEY_FILE point at files anywhere else in the container. KALSHI_PRIVATE_KEY_B64 carries the PEM inline.
Share a ticker file with your bot
Create tickers.txt next to compose.yaml, and put in it the exact open market tickers your bot uses, one per line or comma-separated. Blank lines, duplicates and # comments are accepted. Add these settings to the simulator service:
environment:
RYSTIC_TOKEN: ${RYSTIC_TOKEN}
RYSTIC_LIVE: "1"
RYSTIC_LIVE_MARKETS_FILE: /config/tickers.txt
volumes:
- rystic-leases:/var/lib/rystic/leases
- ./tickers.txt:/config/tickers.txt:ro
Mount the same host file into your bot service, and configure the bot to read /config/tickers.txt with its own file option. Rystic doesn’t prescribe the bot’s flag or variable name, and both containers read the same list without a shell wrapper:
volumes:
- ./tickers.txt:/config/tickers.txt:ro
The equivalent explicit command is command: ["rystic", "run", "--live", "--live-markets-file", "/config/tickers.txt"]. Use either RYSTIC_LIVE_MARKETS or the file, not both. Rystic reads the file once at startup and doesn’t watch it, and the state UI can change the watch list afterward. After editing the file, recreate the services so both processes and bind mounts read the new contents:
docker compose up -d --force-recreate
A CLI rystic restart reuses the resolved startup list recorded for that instance. Explicit tickers survive activity and offer-size filters, but must still be open and within RYSTIC_LIVE_SERIES. The feeder log names tickers that aren’t available.
Share an events file with your bot
If your bot trades by event, share event tickers instead of market tickers. Put them in events.txt, in the same format as tickers.txt, and set RYSTIC_LIVE_EVENTS_FILE (or pass --live-events-file):
environment:
RYSTIC_LIVE: "1"
RYSTIC_LIVE_EVENTS_FILE: /config/events.txt
volumes:
- ./events.txt:/config/events.txt:ro
- The feeder carries every open market of each named event. It looks each event up again on every rescan, so a strike the exchange adds later is carried from the next scan, and a market that closes is dropped.
- Name event tickers (
KXBTCD-26OCT0617), not series (KXBTCD) or market tickers. The feeder log names a series or an open market listed as an event, with the flag that carries it. - An event with no open markets carries nothing and is named once in the feeder log. Name an upcoming event ahead of time, and its markets are carried once they open.
- Events add to
RYSTIC_LIVE_MARKETSor its file, so set both to carry named markets as well.RYSTIC_LIVE_EVENTSand the events file are alternatives, like their market equivalents. --live-seriesdoesn’t narrow named events: each one is looked up directly.- Like the markets file, the events file is read once at startup, and a CLI
rystic restartreuses the resolved event list.