Execution Mechanics: Orders, Slippage & the Bracket
The signal decides what to do; execution decides whether it actually happens — and a stop that isn't confirmed live is not a stop.
The gap between intent and reality
The signal engine produced a direction. The position sizer produced a size. Now the agent must translate that decision into a real order on a real exchange — and this is where most of the operational complexity lives.
Execution is not a formality. The price you intend to trade at is not the price you fill at. The stop you place is not necessarily the stop the exchange acknowledges. The order you submit might get rejected, partially filled, or acknowledged with a status that does not mean what you think it means. A production execution layer handles all of these cases explicitly, because the market does not give partial credit for almost-right implementations.
Order types and their tradeoffs
Three order types cover the vast majority of autonomous trading use cases. Each solves a different problem.
Market orders fill immediately at the best available price. They are the right choice when fill certainty matters more than fill price: stopping out of a position that has gone against you, executing a bracket entry where the signal window is narrow, or closing a position at session end regardless of price. The cost is slippage — in thin books, market orders can fill meaningfully worse than the quoted price, and that gap is invisible in a backtest that assumed midpoint fills.
Limit orders fill at your specified price or better. They are the default for most bracket entries and take-profit targets because you define the worst acceptable price. The tradeoff is fill certainty: if the market doesn't reach your price, you don't fill. For entries, a limit slightly inside the spread often captures most of the spread savings while filling more reliably than post-only.
Post-only limit orders are limit orders that cancel if they would immediately match (turning them into taker orders). They are guaranteed to receive the maker rebate — the exchange pays you for adding liquidity rather than taking it. In strategies where fee efficiency is material to edge, post-only is the appropriate default for entries. The acceptance is that some orders are cancelled instead of filled, requiring the agent to re-evaluate rather than enter.
Modeling slippage honestly
Slippage is the difference between the price you expected to trade at (the price when the signal fired) and the price you actually filled at. It is always a cost. In backtests it is usually modeled at zero or at a fixed optimistic estimate. In production it is real and variable.
For market orders in liquid markets, slippage is typically the half-spread — you're crossing from bid to ask. In thin books or large size relative to the book depth, it can be multiples of the spread. If your backtest assumed midpoint fills and production shows taker fills in a thin book, your live P&L will underperform your backtest by exactly the per-trade slippage.
Model slippage explicitly in your backtest: use the taker price (not midpoint), add a slippage factor based on typical book depth at your trade size, and verify that the strategy remains profitable under realistic assumptions. A signal that is profitable with zero slippage and unprofitable with realistic slippage has no edge — it has an illusion of edge created by backtest optimism.
The bracket: one atomic intent
A bracket is not three separate orders. It is one atomic trade intent with three legs: an entry, a protective stop, and a take-profit target. All three must be live for the intent to be complete.
The sequence matters. You place the entry first and wait for it to fill — because the stop and take-profit orders are orders to close a position that must exist before you place them. Once filled, you place the protective stop. Then — and this is the rule — you confirm the stop is live before placing the take-profit.
The confirm-or-halt rule exists because stop placement can fail silently. The API call returns 200. The orderId comes back. But the exchange rejected the stop logic (wrong price relative to current price, insufficient margin for the stop itself, exchange rate-limiting) and the order status is REJECTED or CANCELLED rather than OPEN. An agent that does not verify the stop status will proceed as if protected when it is not.
Idempotent clientOrderId
Retry logic is necessary in execution. Networks fail. Exchange APIs timeout. Rate limits interrupt submissions. The agent needs to retry — but it must not create duplicate orders.
The solution is an idempotent clientOrderId: a string that uniquely identifies this specific order submission and remains the same across retries. The exchange uses this ID to deduplicate: if it has already processed an order with this clientOrderId, it returns the existing order rather than creating a new one.
The correct construction: concatenate the trade's stable properties — symbol, side, and a unique intentId that represents this particular trade decision. Do not include a timestamp. Do not include a random component. Those turn every retry into a new order and defeat the purpose entirely.
For a bracket, use the same intentId for all three legs with a suffix: intentId + "-stop" for the stop, intentId + "-tp" for the take-profit. Each leg has its own unique but deterministic clientOrderId, so each leg is independently idempotent.
The attempt breaker
Retry logic without a limit is a runaway loop. In a live trading context, a runaway retry loop has concrete costs: each failed attempt might incur exchange API usage, and the fee burns accumulate. More importantly, if the exchange is rejecting for a real reason (insufficient margin, invalid price, account-level restriction), retrying indefinitely masks a real problem while compounding its effects.
The attempt breaker pattern: define a maximum number of attempts (typically 3 for order submission). After the last attempt fails, throw a HaltSignal rather than continuing. The halt gets the agent out of the retry loop and requires operator review before the agent resumes. This is the correct behavior: when something that should work reliably fails three times in a row, the situation warrants human attention, not continued automation.
Build it: placeBracket with confirm-or-halt
You are going to implement the bracket placement function: idempotent clientOrderId, submit entry, await fill, place stop, confirm the stop is live or halt, then place take-profit.
What comes next
You now have the three core modules of an autonomous trading agent: signal engine, position sizer, and execution layer. The next lesson covers what happens when any of these modules misbehave at scale — risk controls, circuit breakers, and the operating procedures that keep an unattended agent from turning a bad day into an account-destroying event.