Skip to content

Order Slip Channel

Topic: order_slip:{user_id}

Costs an order before you place it. Register the order you are considering, and this channel replies with what it would cost you (risk, fee, how much of it would fill immediately and at which prices), then re-pushes those numbers every time the book moves underneath it.

Registering an order here places nothing. Nothing is sent to the matching engine, no funds are committed, and the numbers are a projection of the book as it stands. Place the order through POST /api/v1/orders when you want it live.

Money and quantities on order_slip: follow the standard wire format: money as a decimal string with a minimum of four decimal places and any further precision preserved, quantities as decimal strings with a minimum of two. Parse both with a variable-scale decimal type.

Field Required Type Meaning
market_id yes string The market’s UUID.
qty yes number Contracts you are considering. Must be positive.
side yes string "buy" or "sell".
max_price yes number That market’s max_price, in dollars: 1 for a market whose REST max_price is "1.0000". Sets the price ceiling the risk and fee are computed against.
limit_price no number Your limit, in dollars (0.55). Must be positive if given. Omit it for a market order; the projection then sweeps the book at any price.

Reply carries a ref identifying this entry. Keep it: it is echoed on every push, and it is what remove_order takes.

["3","4","order_slip:<user_id>","add_order",{"market_id":"687e9cdb-a391-4118-aa80-1122bb14779f","qty":100,"side":"buy","limit_price":0.55,"max_price":1}]
{"status":"ok","response":{"ref":"8f3a1c22-7b40-4e19-9d51-2c7e6a8b0f33","self_match":false}}

You may hold 10 registered entries at once. An eleventh is refused with max_orders_reached. Remove one first.

Entries live only as long as the channel does. They are not restored after a disconnect: rejoin, then re-add everything you were tracking.

["3","5","order_slip:<user_id>","remove_order",{"ref":"8f3a1c22-7b40-4e19-9d51-2c7e6a8b0f33"}]
{"status":"ok","response":{}}

add_order and remove_order reply {"status":"error","response":{"reason":"..."}}.

reason Cause
missing required field One of market_id, qty, side or max_price is absent.
market_id must be a string market_id was sent as something other than a string.
invalid market_id market_id is a string but not a UUID.
side must be 'buy' or 'sell' Any other value.
qty must be a number, max_price must be a number, limit_price must be a number That field was not a number or a numeric string. 5 and "5" are both accepted; true, null, a list or an object are not.
invalid decimal The value is a string or float but could not be read as a decimal: "abc", "".
qty must be positive, max_price must be positive, limit_price must be positive Zero or negative.
max_orders_reached You already hold 10 entries.
limit_exceeded The market’s order book refused the registration because this connection already holds its per-market maximum. Both limits are 10 by default, so max_orders_reached is normally hit first and this is not seen; it becomes reachable when the two are configured apart. Remove an entry on that market.
market_unavailable That market has no order book right now. Transient on pre_open, open, closed and cancelled, where a book exists and may be restarting; retry. Permanent on scheduled, resulted and voided, where no book is ever started, so retrying cannot succeed.
fee_unavailable Fees could not be determined for your account on that market.
not_found remove_order was given a ref that is not registered.
ref required remove_order was sent without a ref.
invalid ref ref is not a UUID.

Every registered entry whose numbers changed, in one frame.

[null, null, "order_slip:<user_id>", "order_numbers_batch", {"updates": [OrderNumbers]}]
  • ref : The entry these numbers are for.
  • market_id : The market the entry is on.
  • self_match : See Self-match below.
  • risk : What the order would cost you if it filled as projected: the filling portion plus, for a limit order, the remainder left resting.
  • fee : Fee on the same basis. The filling portion is charged the taker rate, any resting remainder the maker rate.
  • risk_with_fee : risk + fee. What to show as the total.
  • fill_qty : Contracts that would fill immediately.
  • unfilled_qty : Contracts that would not. For a limit order this is what rests on the book; for a market order it is what the book cannot cover.
  • est_fill_at_price : The immediate fill, broken down by price level, best price first. Each entry is price, qty and fee. A resting remainder is not a level here; this lists only what fills now.
[
null,
null,
"order_slip:a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"order_numbers_batch",
{
"updates": [
{
"ref": "8f3a1c22-7b40-4e19-9d51-2c7e6a8b0f33",
"market_id": "687e9cdb-a391-4118-aa80-1122bb14779f",
"self_match": false,
"risk": "54.2000",
"fee": "0.5000",
"risk_with_fee": "54.7000",
"fill_qty": "60.00",
"unfilled_qty": "40.00",
"est_fill_at_price": [
{"price": "0.5300", "qty": "40.00", "fee": "0.2000"},
{"price": "0.5500", "qty": "20.00", "fee": "0.1000"}
]
}
]
}
]

Working that example through, for a buy of 100 at a limit of 0.5500 on a market whose max_price is 1:

Quantity Price Risk Fee
Fills now 40 0.5300 21.2000 0.2000 taker
Fills now 20 0.5500 11.0000 0.1000 taker
Rests 40 0.5500 22.0000 0.2000 maker
Total 54.2000 0.5000

risk counts the resting remainder, so it does not equal the sum of est_fill_at_price; that list is only what fills now. risk_with_fee is 54.2000 + 0.5000.

Risk per contract is the price for a buy and max_price - price for a sell: what you stand to lose either way, not what you pay.

A newly registered entry is costed immediately; its first order_numbers_batch arrives on registration, not on the next tick.

After that, nothing is pushed while the book is still, so silence normally means the projection has not changed.

[null, null, "order_slip:<user_id>", "self_match_batch", {"updates": [{"ref": "...", "self_match": true}]}]

Only entries whose flag actually flipped are included.

self_match is true when you already hold an open order on the opposite side within crossing range of the entry (for a market order, any opposite-side order at all).

The exchange rejects a self-crossing order outright rather than filling around it. So when this flag is true, the numbers alongside it describe a fill that cannot currently happen, and placing the order would be refused.

The entry stays registered anyway, because the condition is yours to clear: cancel the order that is in the way and the flag clears by itself. That is what self_match_batch tells you: the book need not have moved for an entry to become placeable, so this is the only signal that it did.

Treat the flag as advisory. It is read from a cache that trails the exchange by a moment, and it fails open: if it cannot be determined at registration, the entry is returned unflagged rather than blocked. The engine remains the authority at placement.

Use case Message to send
Join ["3","3","order_slip:<user_id>","phx_join",{}]
Cost a limit order ["3","4","order_slip:<user_id>","add_order",{"market_id":"<uuid>","qty":100,"side":"buy","limit_price":0.55,"max_price":1}]
Cost a market order ["3","5","order_slip:<user_id>","add_order",{"market_id":"<uuid>","qty":100,"side":"buy","max_price":1}]
Stop tracking one entry ["3","6","order_slip:<user_id>","remove_order",{"ref":"<ref>"}]
Check the connection is alive ["3","7","order_slip:<user_id>","ping",{}]
["3","3","order_slip:<user_id>","phx_join",{}]
{"status":"ok","response":{}}

No snapshot and no filter. The join reply is empty and nothing follows until you register an entry. Joining a topic whose user id is not yours fails with {"reason":"unauthorized"}.

v1.5.9Changelogllms.txtllms-full.txt