Quant Trading Systems: Architecture & Infrastructure › Execution Management System
Quantitative FinanceAll chapters Quant Trading Systems: Architecture & Infrastructure › Execution Management SystemOne section · Sections in this chapter7 / 14 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
The Execution Management System (EMS) turns trading decisions into executed orders. Building on the execution algorithms covered in the previous chapter, it manages order routing, protocol translation, and fill tracking, serving as the bridge between internal trading logic and external exchanges.
A well-designed EMS abstracts away the complexity of individual exchange protocols, allowing your strategy to work with a uniform interface regardless of which venues receive the orders. It also handles the many operational details that arise in real trading: partial fills, order modifications, exchange outages, and communication failures.
FIX Protocol Integration
The Financial Information eXchange (FIX) protocol is the industry standard for electronic trading communication. Understanding FIX is essential for anyone building trading infrastructure. FIX defines a standard message format for communicating orders, executions, and other trading-related information between counterparties.
The protocol uses a tag-value format where each field in a message is identified by a numeric tag. For example, tag 35 identifies the message type, tag 55 identifies the symbol, and tag 54 identifies the order side. This standardization allows systems from different vendors to communicate without custom integration for each pairing.
# Demonstrate FIX message structure (simplified representation)
# Real FIX uses tag=value format with SOH delimiters
class SimpleFIXMessage:
"""Simplified FIX message builder for demonstration."""
# Common FIX tags
TAGS = {
"BeginString": "8",
"MsgType": "35",
"SenderCompID": "49",
"TargetCompID": "56",
"ClOrdID": "11",
"Symbol": "55",
"Side": "54",
"OrderQty": "38",
"OrdType": "40",
"Price": "44",
"TransactTime": "60",
}
# Message types
MSG_TYPES = {
"NewOrderSingle": "D",
"ExecutionReport": "8",
"OrderCancelRequest": "F",
}
def __init__(self, msg_type: str):
self.fields = {
"BeginString": "FIX.4.4",
"MsgType": self.MSG_TYPES.get(msg_type, msg_type),
}
def set_field(self, name: str, value: str):
self.fields[name] = value
return self
def to_string(self) -> str:
"""Convert to FIX-like string representation."""
parts = []
for name, value in self.fields.items():
tag = self.TAGS.get(name, name)
parts.append(f"{tag}={value}")
return "|".join(parts)
# Build a sample new order message
new_order = SimpleFIXMessage("NewOrderSingle")
new_order.set_field("SenderCompID", "QUANT_FIRM")
new_order.set_field("TargetCompID", "EXCHANGE")
new_order.set_field("ClOrdID", "ORD-2024-001")
new_order.set_field("Symbol", "AAPL")
new_order.set_field("Side", "1") # 1 = Buy
new_order.set_field("OrderQty", "100")
new_order.set_field("OrdType", "2") # 2 = Limit
new_order.set_field("Price", "185.50")Sample FIX New Order Single Message: 8=FIX.4.4|35=D|49=QUANT_FIRM|56=EXCHANGE|11=ORD-2024-001|55=AAPL|54=1|38=100|40=2|44=185.50
Production systems use FIX libraries like QuickFIX to handle the complexities of session management, message sequencing, and heartbeat monitoring. The message structure above is simplified, but illustrates the tag-value format that defines FIX communication. In the output above, tag 35 (MsgType) is 'D' for New Order Single, tag 54 (Side) is '1' for Buy, and tag 40 (OrdType) is '2' for Limit. Understanding these conventions is essential for debugging order routing issues and integrating with new execution venues.
Order Routing and Smart Order Routing
Smart Order Routing (SOR) determines the optimal destination for each order. In fragmented markets with multiple venues (NYSE, NASDAQ, BATS, IEX, dark pools), routing decisions significantly impact execution quality. The same order routed to different venues can result in different execution prices and fill rates.
The challenge of smart order routing arises from market fragmentation. In the US equity market, the same stock can be traded on over a dozen different venues, each with different prices, liquidity, and fee structures. A naive approach of always routing to the exchange with the best displayed price ignores factors like queue position, fill probability, and maker-taker fee economics.
@dataclass
class VenueQuote:
"""Quote from a specific trading venue."""
venue_id: str
bid_price: float
bid_size: int
ask_price: float
ask_size: int
latency_ms: float # Expected latency to venue
fee_per_share: float # Negative means rebate
class SmartOrderRouter:
"""Simple smart order router for demonstration."""
def __init__(self, venues: list[str]):
self.venues = venues
self.venue_quotes: dict[str, VenueQuote] = {}
def update_quote(self, quote: VenueQuote):
"""Update quote for a venue."""
self.venue_quotes[quote.venue_id] = quote
def route_order(
self, side: str, quantity: int, strategy: str = "best_price"
) -> list[tuple[str, int]]:
"""
Route order across venues.
Returns list of (venue_id, quantity) allocations.
"""
if not self.venue_quotes:
return []
if strategy == "best_price":
return self._route_best_price(side, quantity)
elif strategy == "minimize_impact":
return self._route_minimize_impact(side, quantity)
else:
raise ValueError(f"Unknown routing strategy: {strategy}")
def _route_best_price(
self, side: str, quantity: int
) -> list[tuple[str, int]]:
"""Route to venues with best prices, sweeping through order book."""
allocations = []
remaining = quantity
if side == "BUY":
# Sort venues by ask price (lowest first), then by fee
sorted_venues = sorted(
self.venue_quotes.values(),
key=lambda v: (v.ask_price, v.fee_per_share),
)
for venue in sorted_venues:
if remaining <= 0:
break
fill_qty = min(remaining, venue.ask_size)
if fill_qty > 0:
allocations.append((venue.venue_id, fill_qty))
remaining -= fill_qty
else:
# Sort by bid price (highest first)
sorted_venues = sorted(
self.venue_quotes.values(),
key=lambda v: (-v.bid_price, v.fee_per_share),
)
for venue in sorted_venues:
if remaining <= 0:
break
fill_qty = min(remaining, venue.bid_size)
if fill_qty > 0:
allocations.append((venue.venue_id, fill_qty))
remaining -= fill_qty
return allocations
def _route_minimize_impact(
self, side: str, quantity: int
) -> list[tuple[str, int]]:
"""Spread order across venues to minimize market impact."""
total_liquidity = sum(
v.ask_size if side == "BUY" else v.bid_size
for v in self.venue_quotes.values()
)
if total_liquidity == 0:
return []
allocations = []
for venue in self.venue_quotes.values():
venue_liquidity = (
venue.ask_size if side == "BUY" else venue.bid_size
)
proportion = venue_liquidity / total_liquidity
venue_qty = int(quantity * proportion)
if venue_qty > 0:
allocations.append((venue.venue_id, venue_qty))
return allocations# Demonstrate smart order routing
router = SmartOrderRouter(["NYSE", "NASDAQ", "BATS", "IEX"])
# Set up venue quotes
quotes = [
VenueQuote("NYSE", 185.48, 500, 185.50, 300, 0.5, 0.003),
VenueQuote("NASDAQ", 185.47, 200, 185.49, 400, 0.3, -0.002), # Rebate
VenueQuote("BATS", 185.48, 300, 185.50, 200, 0.4, 0.001),
VenueQuote("IEX", 185.47, 400, 185.51, 600, 0.8, 0.0009),
]
for quote in quotes:
router.update_quote(quote)
# Route a buy order using different strategies
buy_qty = 500
allocations_best = router.route_order("BUY", buy_qty, "best_price")
allocations_impact = router.route_order("BUY", buy_qty, "minimize_impact")Buy 500 shares - Venue Quotes: ------------------------------------------------------------ NYSE : 185.48 ( 500) x ( 300) 185.50 fee: $0.0030 NASDAQ : 185.47 ( 200) x ( 400) 185.49 fee: $-0.0020 BATS : 185.48 ( 300) x ( 200) 185.50 fee: $0.0010 IEX : 185.47 ( 400) x ( 600) 185.51 fee: $0.0009 Best Price Routing: NASDAQ: 400 shares BATS: 100 shares Minimize Impact Routing: NYSE: 100 shares NASDAQ: 133 shares BATS: 66 shares IEX: 200 shares
The best-price strategy fills at NASDAQ first (lowest ask at 185.49), while the minimize-impact strategy spreads the order proportionally across venues based on available liquidity. The best-price approach optimizes for immediate execution cost, while the minimize-impact approach reduces the information leakage that occurs when a large order sweeps all liquidity at a single venue.