Order Management & FIX Integration
Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.
From Signals To Orders
Why this matters
Now that we've covered Low-Latency C++ for HFT, Networking & Kernel Bypass, and Concurrency & Lock-Free Programming, we're ready to connect your trading logic to the outside world.
A strategy does not trade directly with an exchange. It asks an Order Management System, or OMS, to create, amend, cancel, and track orders safely.
In HFT, the OMS must be:
- Fast: avoid unnecessary allocation and blocking
- Correct: never lose track of order state
- Deterministic: handle duplicate, delayed, and rejected messages
- Auditable: every action must be explainable later
Today we'll build the mental model and C++ building blocks for order lifecycle handling and basic FIX integration.

OMS Responsibilities
The OMS is the safety layer
The OMS sits between your strategy and the venue.
Strategy -> OMS -> FIX Gateway -> Exchange
<- <- <-
fills, rejects, cancels, acks
A minimal OMS owns these responsibilities:
-
Order identity
- Generate client order IDs
- Map client IDs to exchange IDs when available
-
State tracking
- New, acknowledged, partially filled, filled, canceled, rejected
-
Risk and validity gates
- Symbol allowed?
- Quantity valid?
- Price within limits?
-
Outbound message creation
- New order
- Cancel request
- Replace/amend request
-
Inbound message handling
- Execution reports
- Rejects
- Cancel acknowledgments
The key idea: your strategy should not guess whether an order is live. It asks the OMS.

Order State Machine
Orders are state machines
An order lifecycle is not just a boolean like isFilled.
Common states:
PendingNew -> Live -> PartiallyFilled -> Filled
|
+-> PendingCancel -> Canceled
+-> Rejected
The OMS should only allow valid transitions. For example:
- You can move from
PendingNewtoLiveafter an acknowledgment. - You can move from
LivetoPartiallyFilledafter a partial fill. - You should not move from
Filledback toLive.
This is where correctness matters more than cleverness. A small explicit state machine beats scattered if flags.
```cpp
#include <iostream>
#include <string>
enum class OrderState {
PendingNew,
Live,
PartiallyFilled,
Filled,
PendingCancel,
Canceled,
Rejected
};
std::string name(OrderState s) {
switch (s) {
case OrderState::PendingNew: return "PendingNew";
case OrderState::Live: return "Live";
case OrderState::PartiallyFilled: return "PartiallyFilled";
case OrderState::Filled: return "Filled";
case OrderState::PendingCancel: return "PendingCancel";
case OrderState::Canceled: return "Canceled";
case OrderState::Rejected: return "Rejected";
}
return "Unknown";
}
bool canTransition(OrderState from, OrderState to) {
if (from == OrderState::PendingNew) {
return to == OrderState::Live || to == OrderState::Rejected;
}
if (from == OrderState::Live) {
return to == OrderState::PartiallyFilled || to == OrderState::Filled ||
to == OrderState::PendingCancel || to == OrderState::Rejected;
}
if (from == OrderState::PartiallyFilled) {
return to == OrderState::Filled || to == OrderState::PendingCancel;
}
if (from == OrderState::PendingCancel) {
return to == OrderState::Canceled || to == OrderState::Filled;
}
return false; // terminal states do not move
}
void apply(OrderState& state, OrderState next) {
std::cout << "Transition " << name(state) << " -> " << name(next);
if (canTransition(state, next)) {
state = next;
std::cout << " accepted\n";
} else {
std::cout << " rejected\n";
}
}
int main() {
OrderState order = OrderState::PendingNew;
apply(order, OrderState::Live);
apply(order, OrderState::PartiallyFilled);
apply(order, OrderState::Filled);
apply(order, OrderState::Live); // invalid
std::cout << "Final state: " << name(order) << "\n";
return 0;
}
```
State Transition Check
An order is currently in Filled state. Your OMS receives a late cancel acknowledgment for that same order.
What should the OMS generally do?
FIX Message Basics
FIX is tag-value messaging
FIX, the Financial Information eXchange protocol, is widely used for order entry and execution reports.
A FIX message is mostly a sequence of numeric tags:
8=FIX.4.4|35=D|49=BUY_SIDE|56=BROKER|11=ORD123|55=AAPL|54=1|38=100|40=2|44=185.25|10=...
In real FIX, fields are separated by the byte SOH (\x01), but logs often display it as |.
Important tags for a simple new order:
-
35=Dmeans NewOrderSingle -
11is ClOrdID, your client order ID -
55is Symbol -
54is Side:1buy,2sell -
38is OrderQty -
40is OrdType:2limit -
44is Price
You do not need to memorize every tag today. You need the pattern: encode outbound intent, decode inbound truth.

Encode New Order
Build messages deliberately
For production FIX, teams usually use a certified FIX engine or a carefully tested internal gateway.
Still, it is useful to understand message construction. The example below builds a simplified human-readable FIX-like NewOrderSingle.
Notice the separation:
- The strategy decides intent.
- The OMS assigns identity and validates state.
- The FIX layer encodes the message.
```cpp
#include <iostream>
#include <sstream>
#include <string>
struct NewOrder {
std::string clOrdId;
std::string symbol;
char side; // '1' buy, '2' sell
int quantity;
double price;
};
std::string encodeNewOrderSingle(const NewOrder& o) {
std::ostringstream msg;
msg << "8=FIX.4.4|";
msg << "35=D|"; // NewOrderSingle
msg << "49=CLIENT|56=BROKER|"; // sender/target example
msg << "11=" << o.clOrdId << "|";
msg << "55=" << o.symbol << "|";
msg << "54=" << o.side << "|";
msg << "38=" << o.quantity << "|";
msg << "40=2|"; // limit order
msg << "44=" << o.price << "|";
msg << "59=0|"; // day order
msg << "10=000|"; // placeholder checksum
return msg.str();
}
int main() {
NewOrder order{"C10001", "AAPL", '1', 100, 185.25};
std::string fix = encodeNewOrderSingle(order);
std::cout << "Outbound FIX-like message:\n" << fix << "\n";
return 0;
}
```
FIX Engines
True or false:
For a production trading system, it is safe to treat FIX as plain string concatenation without validating required tags, sequence numbers, sessions, rejects, or checksums.
Handle Execution Reports
Inbound messages update truth
Once an order is sent, the exchange or broker responds with execution reports.
A simplified execution report might contain:
-
35=8: ExecutionReport -
11: ClOrdID -
150: ExecType, such as new, trade, canceled, rejected -
39: OrdStatus -
14: CumQty, cumulative filled quantity -
151: LeavesQty, remaining quantity
The OMS should update state from these reports, not from optimism. Sending an order does not mean it is live. Sending a cancel does not mean it is canceled.
```cpp
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
std::unordered_map<std::string, std::string> parseFixLike(const std::string& msg) {
std::unordered_map<std::string, std::string> fields;
std::stringstream ss(msg);
std::string token;
while (std::getline(ss, token, '|')) {
auto pos = token.find('=');
if (pos != std::string::npos) {
fields[token.substr(0, pos)] = token.substr(pos + 1);
}
}
return fields;
}
std::string orderStatusName(const std::string& status) {
if (status == "0") return "New";
if (status == "1") return "PartiallyFilled";
if (status == "2") return "Filled";
if (status == "4") return "Canceled";
if (status == "8") return "Rejected";
return "Unknown";
}
int main() {
std::string report = "8=FIX.4.4|35=8|11=C10001|150=F|39=1|14=40|151=60|55=AAPL|";
auto f = parseFixLike(report);
std::cout << "Inbound ExecutionReport\n";
std::cout << "ClOrdID: " << f["11"] << "\n";
std::cout << "Symbol: " << f["55"] << "\n";
std::cout << "Status: " << orderStatusName(f["39"]) << "\n";
std::cout << "CumQty: " << f["14"] << "\n";
std::cout << "LeavesQty: " << f["151"] << "\n";
return 0;
}
```
Tag Recall
In FIX, the client-generated order identifier used to track an order across outbound requests and inbound reports is tag ____.
Production Guardrails
What changes in real systems
The lesson examples were intentionally small. A production OMS/FIX path adds several guardrails:
- Session sequence numbers: detect gaps and duplicates
- Heartbeats and test requests: confirm the session is alive
- Resend logic: recover after disconnects
- Idempotency: avoid double-processing duplicate execution reports
- Risk checks: reject invalid orders before they leave your system
- Audit trail: persist every request, acknowledgment, reject, and fill
- Backpressure policy: decide what happens when outbound or inbound queues fill
Connect this to the previous lesson: your lock-free queues can move events quickly, but the OMS must still preserve order identity and legal state transitions.
Next, we'll shift attention to Market Data Handling & Order Book Reconstruction, where the same care around sequence numbers and event ordering becomes crucial on the data side.
