# Orders and matching

A price-time-priority matching engine behind Kalshi's V2 order routes, with retired V1 writes, fees, partial fills and two accounts trading one book.

Orders your bot places cross the simulator's book as they would cross Kalshi's. Fills, position and balance changes and the WebSocket `fill` and `market_position` pushes all follow, identically on every run.

Here `maker` rests an ask and `taker` crosses it. The `empty_book` scenario seeds `KXDEMO-26-A`, a complete market record with no quotes. Balances are per identity, so fund both. All four fields are required: `balance` is cents and `balance_dollars` the same money as a decimal string.

```bash
export RYSTIC_URL=$(rystic run kalshi-twin -d --port 0 --name trade --scenario empty_book --json | jq -r .data.url)
curl -s -X POST "$RYSTIC_URL/_rystic/seed" -d '{"state_balance":{"maker":{"balance":1000000,"balance_dollars":"10000.0000","portfolio_value":0,"updated_ts":1784989800},"taker":{"balance":1000000,"balance_dollars":"10000.0000","portfolio_value":0,"updated_ts":1784989800}}}'
curl -s -X POST "$RYSTIC_URL/trade-api/v2/portfolio/events/orders" -H 'Content-Type: application/json' -H 'X-Rystic-Actor: maker' -d '{"ticker":"KXDEMO-26-A","client_order_id":"m1","side":"ask","count":"10","price":"0.40","time_in_force":"good_till_canceled","self_trade_prevention_type":"taker_at_cross"}' | jq -c '{order_id, fill_count, remaining_count}'
# -> {"order_id":"a7cade2e-8c1a-43f4-8000-000000000001","fill_count":"0.00","remaining_count":"10.00"}
curl -s "$RYSTIC_URL/trade-api/v2/markets/KXDEMO-26-A/orderbook"
# -> {"orderbook_fp":{"no_dollars":[["0.6000","10.00"]],"yes_dollars":[]}}
curl -s -X POST "$RYSTIC_URL/trade-api/v2/portfolio/events/orders" -H 'Content-Type: application/json' -H 'X-Rystic-Actor: taker' -d '{"ticker":"KXDEMO-26-A","client_order_id":"t1","side":"bid","count":"10","price":"0.40","time_in_force":"fill_or_kill","self_trade_prevention_type":"taker_at_cross"}' | jq -c '{fill_count, average_fill_price}'
# -> {"fill_count":"10.00","average_fill_price":"0.4000"}
curl -s "$RYSTIC_URL/trade-api/v2/portfolio/fills" -H 'X-Rystic-Actor: maker' | jq -c '.fills[] | {is_taker, count_fp, yes_price_dollars}'
# -> {"is_taker":false,"count_fp":"10.00","yes_price_dollars":"0.4000"}
curl -s "$RYSTIC_URL/trade-api/v2/portfolio/positions" -H 'X-Rystic-Actor: maker' | jq -c '.market_positions[] | {ticker, position_fp}'
# -> {"ticker":"KXDEMO-26-A","position_fp":"-10.00"}
```

Read each side back with the header you traded under: a plain `curl` reads `member`'s account, which holds no order. `maker` is short 10 because it sold 10 YES.

### The V2 order routes

Trade through `/portfolio/events/orders`, in the book-side convention `{"side": "bid"|"ask", "price", "count"}`. Creating an order requires `time_in_force`: `fill_or_kill`, `good_till_canceled` or `immediate_or_cancel`. Creating, amending, decreasing, canceling and batching orders all act on the simulator's own matching book. That's the book `GET /markets/{ticker}/orderbook` returns, and no order reaches Kalshi. Quotes in the RFQ desk are addressable both flat (`/communications/quotes/{id}`) and nested under their RFQ.

- **Partial fills are normal.** A marketable limit sweeps whatever depth is on the book and the remainder rests, with counts in 2-decimal fixed point. The `fill_count` and `remaining_count` in the response to creating an order are the signal, not an error code.
- **The cancel-all kill switch returns 404.** `DELETE /portfolio/events/orders` returns `404`, as measured on Kalshi's demo exchange, and leaves your orders resting. Cancel by id or with `DELETE /portfolio/events/orders/batched`.
- **Follow `GET /portfolio/orders`' `cursor` until it comes back empty** ([Pagination](https://www.rystic.ai/docs/simulators/kalshi.md#pagination)). A reconciler that treats page one as the whole book breaks once it holds more than a page of orders: it decides the rest were canceled. The live API does the same to that bot.
- **The taker's fee follows the fee schedule:** `ceil(0.07 × fee_multiplier × count × price × (1 − price))` per fill, at the micro-dollar. `fee_multiplier` is the series' own: `1` on every production series, `KXBTC15M` included. Fees are per series, and `KXDEMO` declares none.
- **Another actor's `order_id` returns 404** rather than leaking.

### The retired V1 order writes

Since June 2026 the live exchange returns `410` to every V1 order write. The simulator returns the same bytes, taken from captures of Kalshi's demo exchange, on each of them:

- `POST /portfolio/orders`
- `POST /portfolio/orders/{order_id}/amend`
- `DELETE /portfolio/orders/{order_id}`
- `DELETE /portfolio/orders/batched`

The body is `{"error":{"code":"deprecated_v1_order_endpoint","message":"Please switch to the V2 endpoints",…}}`, so a bot built here fails the way it would in production.

A V1-era client you can't change yet keeps its outcome-side wire (`{"side": "yes"|"no", "action", "count_fp", "yes_price_dollars"|"no_price_dollars"}`) when the simulator starts with `RYSTIC_KALSHI_LEGACY_ORDERS=on`.

**Differs from Kalshi:** with `RYSTIC_KALSHI_LEGACY_ORDERS=on`, the V1 order writes work, where Kalshi returns `410` on those paths. The setting is a hosting convenience, not a claim about the live exchange.

### Matching, queue position and limits

Matching on a hand-seeded or scenario book follows price-time priority. A replayed tape adds recorded flow, and [what replay does not model](https://www.rystic.ai/docs/simulators/kalshi/features/tape-format.md#what-replay-does-not-model) says what it doesn't model. Live two-account tests on Kalshi's demo exchange confirmed these matching rules:

- price-time priority
- amending an order loses its place in the queue, and decreasing one keeps it
- a resting order holds no buying power
- a batch can be accepted in part
- the taker fee math

The queue position of a resting order is at `GET /portfolio/orders/{order_id}/queue_position`. One process holds one exchange: run several simulators on different ports for isolated books.

---

Source: https://www.rystic.ai/docs/simulators/kalshi/features/orders-and-matching · Markdown: https://www.rystic.ai/docs/simulators/kalshi/features/orders-and-matching.md
