Skip to Content
SimulatorsKalshiLive feed settings and Compose

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

FlagEnvDefaultMeaning
--liveRYSTIC_LIVE=1offfeed the simulator from the exchange
--live-carry NRYSTIC_LIVE_CARRY0pre-carry the busiest N markets; 0 carries nothing until the state UI picks; ignored when markets or events are named
--live-markets a,bRYSTIC_LIVE_MARKETSnonecarry exactly these markets before anything is picked; the state UI can change the list later
--live-markets-file pathRYSTIC_LIVE_MARKETS_FILEnoneread the startup watch list from a shared file; an alternative to --live-markets
--live-events a,bRYSTIC_LIVE_EVENTSnonecarry every open market of these events, looked up again on every rescan; adds to --live-markets
--live-events-file pathRYSTIC_LIVE_EVENTS_FILEnoneread the startup event list from a shared file; an alternative to --live-events
--live-series a,bRYSTIC_LIVE_SERIES*series to carry; * offers every open market on the exchange
--live-max-markets NRYSTIC_LIVE_MAX_MARKETS500how many of the busiest open markets the state UI offers
--live-shards NRYSTIC_LIVE_SHARDS3exchange sockets the carried markets spread over, at most 5
--live-envRYSTIC_LIVE_ENVthe 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.

  1. The exchange socket lives in the CLI process, under --runner docker too, where the feeder runs on the host beside the container.
  2. --live-shards plus the sockets of any recorder running on the same key must stay at or under Kalshi’s limit of five.
  3. stop ends the feeder first, then the simulator. restart re-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 the acl package
  • 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
  1. 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.
  2. 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.
  3. 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.
  4. Events add to RYSTIC_LIVE_MARKETS or its file, so set both to carry named markets as well. RYSTIC_LIVE_EVENTS and the events file are alternatives, like their market equivalents.
  5. --live-series doesn’t narrow named events: each one is looked up directly.
  6. Like the markets file, the events file is read once at startup, and a CLI rystic restart reuses the resolved event list.
Last updated on