Skip to main content
You never pass a symbol — the client already has one.

What happens to your order

A limit order is never split. Splitting one across venues is not one order at a price — it is several orders in several queues, which behaves differently and is not something SORY will do without you asking.

place()

Returns an Order.

How long an order lives

A limit order rests until it is filled or cancelled. That is what every venue does when it is sent no time-in-force at all, so SORY sends none — there is no parameter, and nothing for a venue to disagree about. No stop orders, no trigger orders, no icebergs, no algos, no post-only, no IOC or FOK. Build those on top.

What you get back

A market order across several venues produces one child per venue. Everything else produces one.
Each VenueOrder has venue, symbol, side, order_type, amount, price, client_order_id and venue_order_id.

Knowing what happened to your order

Three ways, depending on what you are doing. The order object stays current. The Order you get back from place() updates itself as the venues report. You do not look it up again:
Or react to each report as it arrives, which is what you want if you are quoting:
Or ask the venues what is still working, which is the only thing that survives a restart:
Or ask the venues about one order directly, when the stream has gone quiet and you need to know before you act:
An order that has reported nothing has not necessarily done nothing. A venue can report a fill late, under an identifier it chose rather than the one SORY sent, or not at all. order.filled == 0 means nothing has been reported, which is a different claim from nothing happened.That difference costs money in both directions: re-sending the remainder of a hedge that already filled leaves you with a position twice over, and reading a silent fill as a miss leaves you unhedged while believing you are flat. Before you act on an absence — retrying, re-sending, giving up — confirm() it. A child that never reached a venue is skipped, and a venue that cannot answer leaves the order untouched rather than guessed at.
open_orders() asks the venues, so it sees everything working under the same API key — including orders left behind by a previous run of your process. SORY stores nothing on disk, so this is the whole recovery story.For just the orders this process sent, sory.store holds them: store.get(id), store.open_orders(), store.all(), store.open_count().

What the venue says back

OrderUpdate is the venue’s own report with the numbers converted to Decimal and the venue name attached. Nothing is computed from anything but the venue’s own numbers; raw holds the untouched payload. The one exception is average, which is filled in as cost / filled when the venue reported both and left the average out — as several do on a market order. That is the venue’s own cost over the venue’s own fill. Without it you would hold a fill you cannot price.

Cancelling

Wait for the confirmation before you repost. A cancel that races a fill is how you end up filled twice, so cancel returns when the venues have answered — not when the requests went out.

State

In memory only. No file, no database. After a restart, ask the venues what is resting — that is the whole recovery story.

Testing safely

Everything runs — routing, rounding, building each venue order — and nothing is sent. It is what catches “this slice is too small for kraken” before a live run does. The order comes back exactly as it would have gone out: order.plan shows the split and why, and order.children holds the venue orders, already rounded. It is one flag on the call you were going to make anyway, so the rehearsal and the real thing cannot drift apart.