Strategies should not directly talk to exchanges. They receive normalized market data and emit order intents.
class StrategyPlugin:
def on_market_data(self, event: MarketDataEvent) -> list[OrderIntent]:
pass
def on_order_update(self, event: OrderUpdateEvent) -> None:
pass
def on_timer(self, timestamp_ns: int) -> list[OrderIntent]:
pass
Example order intent:
{
"idempotency_key": "strategyA-AAPL-20260518T143001.123456789-buy-001",
"strategy_id": "mean_reversion_v7",
"account_id": "acct_123",
"symbol": "AAPL",
"venue": "NASDAQ",
"side": "BUY",
"order_type": "LIMIT",
"quantity": 1000,
"limit_price": "184.25",
"time_in_force": "IOC",
"client_order_id": "coid_abc123"
}
Prefer binary streaming protocols for production hot paths.
service MarketDataService {
rpc SubscribeMarketData(MarketDataRequest) returns (stream MarketDataEvent);
}
message MarketDataRequest {
repeated string symbols = 1;
repeated string venues = 2;
bool full_order_book = 3;
}
Internal strategy-facing API:
service OrderService {
rpc SubmitOrder(OrderIntent) returns (OrderAck);
rpc CancelOrder(CancelRequest) returns (CancelAck);
rpc ReplaceOrder(ReplaceRequest) returns (ReplaceAck);
}
Important fields:
{
"client_order_id": "coid_abc123",
"idempotency_key": "unique-request-key",
"strategy_id": "strategy_1",
"account_id": "acct_1",
"symbol": "ETH-USD",
"side": "BUY",
"quantity": "5.0",
"limit_price": "3100.25"
}
POST /risk/accounts/{account_id}/limits
{
"max_position_by_symbol": {
"AAPL": 100000
},
"max_notional_per_order": "500000",
"max_daily_loss": "1000000",
"max_orders_per_second": 500,
"allowed_symbols": ["AAPL", "MSFT", "NVDA"],
"allowed_venues": ["NASDAQ", "NYSE"]
}
POST /backtests
{
"strategy_artifact_id": "mean_reversion_v7",
"dataset": {
"venue": "NASDAQ",
"symbols": ["AAPL", "MSFT"],
"start_time": "2026-01-01T09:30:00-05:00",
"end_time": "2026-01-31T16:00:00-05:00"
},
"simulation": {
"latency_model": "p99-production-latency",
"fee_model": "nasdaq-tier-2",
"fill_model": "queue-position-aware",
"slippage_model": "volatility-adjusted"
}
}
The platform should be split into a latency-sensitive trading hot path and a scalable analytics/backtesting cold path.
The hot path handles live trading:
The hot path should avoid heavy databases, distributed joins, synchronous analytics queries, and non-deterministic dependency calls.
The cold path handles backtesting, research, analytics, and reporting:
Production trading and backtesting must share the same strategy API and market-data event model. Otherwise, strategies may perform well in backtests but fail in production due to mismatched assumptions.
Responsible for connecting to exchange feeds and normalizing tick data.
Maintains real-time full-depth order books.
Action:
Action:
The event bus distributes market events to strategies.
Use two buses:
This prevents analytics workloads from affecting trading latency.
Runs user-defined trading algorithms.
Strategies should run in isolated sandboxes:
| Mode Description | |
| Backtest | Replays historical data offline |
| Paper | Uses live data but simulated orders |
| Shadow | Runs beside production but does not trade |
| Canary | Trades small size with strict risk limits |
| Live | Fully active production trading |
The Risk Engine is the most critical safety component.
Risk checks should be performed in-memory on the hot path.
Use strongly consistent state for:
The Risk Engine should fail closed.
If position state, limits, market data, or account state is unavailable, reject the order.
The OMS tracks every order from creation to terminal state.
CREATED
-> RISK_REJECTED
-> RISK_APPROVED
-> SENT
-> ACKED
-> PARTIALLY_FILLED
-> FILLED
-> CANCELED
-> REJECTED
-> EXPIRED
Problem:
A strategy retries SubmitOrder because it did not receive an acknowledgement.
Solution:
Example:
Idempotency-Key: strategyA-AAPL-20260518T143001-buy-001
This prevents duplicate orders during client retries, network timeouts, OMS failover, or exchange gateway reconnection.
Responsible for venue-specific order transmission.
Problem:
Exchange allows only a certain number of order messages per second.
Solution:
Recommended behavior:
| Message Type Priority | |
| Cancel risky order | Highest |
| Reduce exposure | High |
| Replace passive order | Medium |
| New aggressive order | Lower |
| Non-critical strategy order | Lowest |
Tracks real-time trading state.
The Risk Engine should use the freshest in-memory position state, while slower reconciliation systems verify correctness asynchronously.
The backtesting engine should reuse production strategy interfaces.
| Metric Description | |
| Total return | Strategy profitability |
| Sharpe ratio | Risk-adjusted return |
| Max drawdown | Worst peak-to-trough loss |
| Win rate | Percentage of profitable trades |
| Average fill latency | Simulated execution delay |
| Slippage | Difference between expected and actual fill |
| Turnover | Trading volume relative to capital |
| Order rejection rate | Rejected orders due to risk/rate/venue rules |
| Capacity | Maximum deployable capital before performance degrades |
| Layer Purpose | |
| Raw tick archive | Immutable exchange feed capture |
| Normalized tick store | Backtesting and analytics |
| Feature store | Precomputed factors and features |
| Audit log | Immutable compliance record |
| Metrics store | Latency, throughput, errors, health |
Partition by:
Example path:
s3://market-data/raw/venue=NASDAQ/symbol=AAPL/date=2026-05-18/hour=09/
Use role-based and attribute-based access control.
Examples:
| Role Permission | |
| Quant Developer | Upload strategy, run backtests |
| Trader | Enable/disable approved strategies |
| Risk Manager | Configure limits, trigger kill switch |
| Admin | Manage users and infrastructure |
| Auditor | Read-only access to audit logs |
| Metric Target | |
| Market data ingest latency p50/p99/p999 | Track by venue and symbol |
| Market data gap count | Near zero |
| Order risk-check latency p99 | Sub-millisecond |
| Order gateway latency p99 | Low milliseconds or better |
| Exchange ack latency | Track per venue |
| Event bus publish latency | Microseconds to low milliseconds |
| Strategy decision latency | Track per strategy |
| Dropped event count | Zero in hot path |
| Duplicate order count | Zero after idempotency |
| Rate-limit rejection count | Alert on spikes |
| Metric Purpose | |
| Order state mismatch count | Detect OMS/exchange inconsistency |
| Position reconciliation drift | Detect PnL or position errors |
| Feed disconnect count | Detect exchange/feed instability |
| Circuit breaker activation count | Detect severe market/platform events |
| Failed risk checks by reason | Detect risky strategy behavior |
| Audit log write failures | Must alert immediately |
| Metric Purpose | |
| Realized PnL | Profitability |
| Unrealized PnL | Current exposure |
| Drawdown | Risk control |
| Fill ratio | Execution quality |
| Slippage | Trading efficiency |
| Market impact | Strategy scalability |
| Capital utilization | Efficiency |
| Strategy capacity | Maximum safe capital allocation |
Market volatility spikes after major news. Thousands of symbols update simultaneously. Many strategies react at the same time and submit a burst of orders.
A strategy sends an order. The OMS processes it, but the network response times out. The strategy retries the same request.
The platform must not create two orders.
idempotency_key on every order, cancel, and replace request.client_order_id.A strategy tries to send 10,000 orders per second, but the exchange allows 1,000 messages per second.
The platform must protect the exchange session, account, and market integrity.
Example rejection:
{
"status": "REJECTED",
"reason": "RATE_LIMIT_EXCEEDED",
"retry_after_ms": 250,
"scope": "VENUE:NDAQ"
}
If risk state cannot be reconstructed, reject new orders until safe.
| Use Case Recommended Store | |
| Hot order state | In-memory plus durable append-only log |
| Audit log | Immutable append-only store |
| Raw tick data | Object storage |
| Normalized historical ticks | Columnar lakehouse format |
| Metrics | Time-series database |
| User/config data | Strongly consistent relational DB |
| Strategy artifacts | Versioned artifact registry |
| Risk limits | Strongly consistent config store |
For HFT, synchronous database writes in the order hot path may be too slow. A common solution is to write to a durable append-only log optimized for sequential writes, then asynchronously project state into query databases.
Allowing arbitrary plug-ins is powerful but dangerous. Use sandboxing, artifact signing, resource limits, and staged deployment.
Strict global ordering does not scale. Use per-symbol, per-account, or per-strategy ordering where correctness requires it.
Tick-level queue-position-aware simulation is expensive but more realistic. Use faster approximate backtests for exploration and slower high-fidelity backtests before deployment.
The platform should use a low-latency event-driven architecture with strict separation between live trading and offline research. The hot path should be optimized for deterministic behavior, bounded latency, risk enforcement, and fault isolation. The cold path should optimize for scalable storage, replayability, analytics, and high-fidelity backtesting.
The most important design principles are: