← All articles

Quant

From signal to order — an execution engine that survives a restart

2026-08-02 · 7 min read

In a backtest, taking a position is one line: position = 1. It always works, it's instant, and it can't fail. Live, that same line becomes a submission that might be acknowledged, might be rejected, might fill in three pieces over ninety seconds, and might be sitting at the exchange right now while your process is dead and knows nothing about it. This is the half of algorithmic trading that nobody blogs about, and it's where real money actually disappears.

The order lifecycle

The order state machine, including crash recovery on restart: ask the broker PENDING SUBMITTED PARTIAL FILLED CANCELLED / REJECTED you died here the true state the broker is the source of truth — your database is a cache
You submitted an order and crashed. While you were down, it partially filled. Nothing in your own database can tell you that — only the broker can.

Every order is a state machine

An order isn't a boolean, it's a lifecycle: PENDING → SUBMITTED → PARTIAL → FILLED, with exits to CANCELLED and REJECTED along the way. Modelling it explicitly sounds like ceremony until you notice how many bugs it eliminates by construction: you cannot cancel an order that was never acknowledged, you cannot double-count a fill, and you always know what you believe about every order in flight.

The rule that makes it work is persist first, act second. Write the intended transition to durable storage before you send anything to the broker. If you send first and record after, the window between them is precisely where you lose track of real money. It's the same reasoning as a database's write-ahead log and the durability guarantee it buys — decide, write it down, then do it.

The three hard problems

Restarts. Your process dies between submit and acknowledgement. On startup, what do you know? Nothing useful — your own state says "SUBMITTED", which was true when you wrote it and might be a lie now. The only fix is to reconcile on every startup: fetch open orders and current positions from the broker, compare against your stored state, and resolve differences in the broker's favour. Always. The broker is the source of truth; your database is a cache of what you last heard.

Duplicates. You submit, the request times out, you retry — and now you might own twice what you intended, because the first one landed after all. The fix is a client order ID: a unique identifier you generate and attach, so a retry with the same ID is recognised and ignored by the venue rather than executed twice. This is exactly idempotency, applied to the highest-stakes retry you'll ever write.

Partial fills. You wanted 1,000; you have 400 and an open order for 600. Are you in the trade? Your backtest never had to answer, because it never happened there. Live, you need a decision for every one of these: chase the rest, cancel and re-quote, or accept a smaller position and size the risk accordingly. Whatever you choose, the strategy has to be able to express "partly on" — if it can't, it isn't describing a state the real world can put it in.

The gist

Backtests set a variable; live systems send a request that can be lost, doubled, half-done, or refused. So you track every order as a state machine, write down what you're about to do before you do it, and always trust the broker over your own memory.

Assumptions that will cost you

  • An acknowledgement is not a fill. The broker saying "received" tells you nothing about your position.
  • A cancel can race with a fill. You cancel; it fills anyway, microseconds earlier. Both messages are true. Your code must handle "cancel rejected — already filled" as a normal Tuesday, not an exception.
  • Fills can arrive late, out of order, or twice. Deduplicate by fill ID; never accumulate position by adding up messages you assume are unique.
  • Your clock is not their clock. Use the venue's timestamps for anything you'll later reconcile against a backtest.
  • Reconnects replay. A dropped socket that comes back may re-deliver messages you already processed.

Fail closed

The most valuable line in an execution engine is the one that gives up safely. If reconciliation can't resolve — your state and the broker's disagree in a way you didn't anticipate — do not guess. Stop trading, cancel what you can, and demand a human. An algo that halts costs you the day's edge; an algo that guesses about its own position can cost considerably more than that, and it does so at machine speed. Wiring that path deliberately is what risk limits and the kill switch are for.

None of this is glamorous, and none of it appears in your Sharpe ratio. But it's the part that decides whether the edge you validated ever reaches your account intact — which is why it gets its own rungs on the deployment ladder, and why the boring engineering patterns from the backend world (idempotency, observability, durable state) turn out to be the actual prerequisites for trading live.


ExecutionState machinesLive tradingQuant
← Back to the blog