Quantitative Trading4 min read

A Retry Is Not a Trading Decision

S

Suneet Malhotra

Aug 27, 2026

β€’
1 views
A Retry Is Not a Trading Decision - Quantitative Trading blog post
πŸ”§PythonπŸ”§Order ManagementπŸ”§Risk ControlsπŸ”§SQLite

A retry is usually described as reliability work. In a trading system, that description is incomplete. A retry changes the system's behavior in the market, so it is a trading decision and needs the same evidence as an entry or an exit.

I ran into this distinction while thinking through rejected orders. The dangerous implementation is easy to write: submit an order, catch an exception, wait, and submit again. The code looks resilient. The state machine is not.

Rejection is not failure

There are at least two broad classes of unsuccessful submission.

The first is transport uncertainty. The client timed out after sending the request, so the system does not know whether the broker accepted it. Retrying immediately may create a duplicate order. The correct next action is reconciliation: query the broker using a client order ID, inspect the order book, and only then decide whether another submission is possible.

The second is a known rejection. The broker or the local risk layer returned a reason: market closed, buying power insufficient, quantity invalid, symbol halted, price outside a band, or the strategy's own limit refused the order. A retry of the same intent is not recovery. It is repetition without new information.

Those paths often share one exception handler because both produce a non-success response. That is a classification bug. The system is treating unknown state and known refusal as the same event.

The state that was missing

An order intent should have a durable identity before it reaches the network. Mine needs more than symbol, side, and quantity. It needs an intent ID, strategy signal ID, created timestamp, limit price, risk snapshot, submission attempt, and a terminal reason when the workflow stops.

The important field is not attempt count. It is the transition reason.

"risk_rejected -> terminal"

"transport_unknown -> reconcile"

"broker_accepted -> monitor"

"broker_rejected_retryable -> retry under policy"

The distinction prevents an operational mechanism from silently creating a new market hypothesis. If the risk layer rejected an order because exposure was already at its cap, a retry does not make the exposure lower. If the broker rejected a stale limit price, a retry might be valid only after the quote or pricing policy changes. The changed input must be explicit.

This is the same principle I use for signals: a decision should be reproducible from the state that produced it. "Try again" is not reproducible. "Retry once after a transport timeout, using the same client order ID, after broker reconciliation" is a rule.

Idempotency has a market meaning

In ordinary software, idempotency is often framed as avoiding duplicate API effects. In a trading engine, it also preserves the meaning of the strategy's action.

Suppose one signal creates an intent to buy 100 shares. The first request times out. A naive retry submits another 100 shares. If both requests filled, the strategy did not buy 100 shares with uncertain plumbing. It bought 200 shares. The second fill is an unintended position change, even if every individual API call was valid.

That makes the client order ID part of the trading model, not just a networking detail. The identifier should be deterministic for the intent and stable across process restarts. A restarted worker must recover the same pending intent from durable storage, not reconstruct a new one from the latest signal.

The practical test is simple: kill the worker after each external boundary. After restart, can it prove whether the intent is pending, accepted, rejected, or unknown? If not, the retry policy is making a portfolio decision out of missing state.

What I would measure

I would not start with "did retries improve success rate?" That metric rewards duplicate submissions unless fills are joined back to intent IDs.

The first dashboard would show:

  • intents by terminal state: filled, canceled, rejected, expired, and unknown
  • retry count by rejection reason
  • fills per intent ID, with duplicates isolated
  • time spent in transport-unknown before reconciliation
  • signals rejected before submission versus orders rejected by the broker

The last two lines matter because they locate the boundary. A strategy can have fewer trades because its rules are selective, because risk controls are active, or because the order path is broken. Total trade count cannot tell those stories apart.

The design conclusion is narrow. Retry transient infrastructure uncertainty, subject to reconciliation and an idempotency key. Do not retry a known trading refusal unless a named input changed and the new attempt is a new, auditable transition.

Reliability is not measured by how often the system tries again. It is measured by whether the system can explain why the portfolio changed once, and not twice.

Share this post

You Might Also Like

Stay in the Loop

Get weekly insights on AI-driven QA, engineering leadership, and automation strategies.

No spam, ever. Unsubscribe anytime.