Skip to content
Course contents
The order and the order manager

Keeping track of every order

An order manager is the part of the system that remembers every order it has sent and what became of it, so the strategy never double-sends or loses track. This chapter builds a small one against the simulated broker, with client order ids, safe retries, and duplicate protection, and explains why idempotency, doing a thing at most once even if you ask twice, is the core idea.

10 min readChapter 12 of 28
What you will learn
  • Build an order manager that records and updates the state of every order
  • Use client order ids and idempotency to prevent duplicate orders on a retry
  • Expose a clean interface the strategy calls instead of the broker directly

Imagine your program sends a buy order, and then the reply gets lost to a network blip or a timeout. Did the order go through or not? If your program simply tries again to be safe, and both attempts actually reached the broker, you have now bought twice. This single problem, the uncertain retry, is why serious trading systems have an order manager: a part whose whole job is to remember every order and make sure each one happens exactly once.

What an order manager is for

An order manager sits between the strategy and the broker, remembering every order by a client id so the system never double-sends or loses track.
An order manager sits between the strategy and the broker, remembering every order by a client id so the system never double-sends or loses track.

An order manager sits between your strategy and the broker. Instead of the strategy calling the broker directly, it asks the order manager to send an order, and the order manager takes responsibility for the messy parts: giving the order a client id, sending it, remembering it, tracking its state as confirmations come back, and, above all, never sending the same order twice. It is the memory and the conscience of the system's order flow. The strategy gets to think in terms of "I want to buy 10," and the order manager handles what that safely requires.

Idempotency: the one idea that matters most

The key idea has an awkward name and a simple meaning. An action is idempotent if doing it twice has the same effect as doing it once. Sending an order is not naturally idempotent: send it twice and you buy twice. The order manager makes it idempotent by using the client id you assign. Before sending, it checks whether it has already sent an order with that id. If it has, it does not send again; it returns what it already knows. Now a retry is safe, because asking twice with the same id buys once. Here is a small order manager that does exactly this.

ExampleAn order manager that dedupes a retry using the client idch12/order_manager.py
# An order manager sends orders through the broker and remembers every one by the
# client id you gave it. Its most important job is idempotency: sending the same
# client id twice must NOT place a second order. That is what stops a retry, after
# a timeout or a lost reply, from silently doubling your position.
from paper_broker import PaperBroker


class OrderManager:
    def __init__(self, broker):
        self.broker = broker
        self.sent = {}                 # client_order_id -> broker order_id

    def send(self, client_order_id, symbol, side, quantity,
             order_type="MARKET", price=None):
        if client_order_id in self.sent:
            print(f"  [dedup] {client_order_id} already sent; not sending again")
            return self.broker.get_order(self.sent[client_order_id])
        order = self.broker.place_order(symbol, side, quantity, order_type, price,
                                        client_order_id=client_order_id)
        self.sent[client_order_id] = order.order_id
        print(f"  [sent]  {client_order_id} -> broker order {order.order_id} ({order.status})")
        return order

    def open_orders(self):
        return [o for o in self.broker.get_orders()
                if o.status in ("OPEN", "PARTIALLY_FILLED")]


broker = PaperBroker(cash=1_000_000, prices={"RELIANCE": 1400})
om = OrderManager(broker)

# The first send places the order.
om.send("entry-001", "RELIANCE", "BUY", 10, "MARKET")
# A retry with the SAME client id (say, after a timeout) does not double-buy.
om.send("entry-001", "RELIANCE", "BUY", 10, "MARKET")
# A different id is a genuinely new order.
om.send("entry-002", "RELIANCE", "BUY", 5, "LIMIT", price=1390)

print("\nOpen orders:", [o.client_order_id for o in om.open_orders()])
print("Position:   ", broker.get_positions())
print("Funds:      ", broker.get_funds())
Output
  [sent]  entry-001 -> broker order 1 (FILLED)
  [dedup] entry-001 already sent; not sending again
  [sent]  entry-002 -> broker order 2 (OPEN)

Open orders: ['entry-002']
Position:    [{'symbol': 'RELIANCE', 'quantity': 10, 'avg_price': 1400.0}]
Funds:       {'available_cash': 986000.0}

Read what happened. The first send of "entry-001" places a real order, which fills. The second send of "entry-001", the retry, is recognised as a duplicate and does not place anything; the order manager just returns the order it already has. The proof is in the position: 10 shares, not 20. Then "entry-002", a genuinely different id, is a new order, which rests as a limit. Without the dedup, that retry would have doubled the position to 20 and spent another 14,000 rupees. The client id, and the order manager that respects it, is what stands between a lost reply and a doubled trade.

A clean interface, and remembered state

Notice the shape of it. The strategy never touches the broker; it calls the order manager's send. This is the separation from the architecture chapter made concrete, and it earns its keep here. Because every order flows through one place, that one place can keep the record: which orders are open, which have filled, which were rejected. The open_orders method is a small taste of that memory. A fuller order manager also updates each order's state as confirmations stream in, so at any moment the system can answer "what orders do I have working, and what has become of them," which is exactly what the strategy and the risk controls need to make their next decision.

What to carry forward

An order manager is the memory and conscience of your order flow: it gives every order a client id, sends it through the broker, remembers it, and above all makes sending idempotent, so that a retry after a lost reply does not double your position. You saw a duplicate send recognised and refused, leaving the position at 10 rather than 20. Because every order passes through this one place, it can also track which orders are open, filled, or rejected, the record the rest of the system reasons from. One thing an order manager cannot do alone is know when its record has drifted from the broker's, and closing that gap is the next chapter: reconciliation.