Major Changes: - Migrated from 3-action TradingAction to 45-action FactoredAction - 45 actions: 5 exposure × 3 order types × 3 urgency levels - Absolute exposure model (target positions -1.0 to +1.0) - Transaction cost differentiation (Market 0.15%, LimitMaker 0.05%, IoC 0.10%) - Fixed action diversity threshold (1.11% → 0.5% for 45-action space) Bug Fixes: - Bug #15: Incomplete FactoredAction integration (code existed but unused) - Bug #16: Runtime crash in action diversity checking (hardcoded 3-action match) Code Changes (13 files, ~464 lines): - ml/src/dqn/action_space.rs: Core FactoredAction + 4 helper methods - ml/src/trainers/dqn.rs: Action diversity refactored (3→45 dynamic) - ml/src/dqn/reward.rs: calculate_reward() signature updated - ml/src/dqn/portfolio_tracker.rs: execute_action() absolute exposure - ml/src/dqn/dqn.rs: WorkingDQN action selection migrated - ml/tests/*.rs: 9 test files updated with FactoredAction assertions Test Results: - 1-epoch smoke test: 100% action diversity (45/45 actions, 80.2s) - 10-epoch production: 87.8% readiness (79/90 scorecard, 14.0 min) - Loss convergence: 96.9% reduction (119K → 3.6K) - Action diversity: 100% → 44% (healthy specialization) - Checkpoint reliability: 12/12 files saved (100%) - DQN tests: 195/195 passing (100%) - ML baseline: 1,514/1,515 passing (99.93%) Production Status: ✅ CERTIFIED (87.8% readiness) Go/No-Go: ✅ GO FOR 100-EPOCH PRODUCTION TRAINING 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
26 KiB
Broker Gateway Service API Reference
Version: 0.1.0
Protocol: gRPC (Protocol Buffers v3)
Base URL: grpc://localhost:50056
Table of Contents
Overview
The Broker Gateway Service provides a gRPC API for order routing, execution management, and account state queries. All methods use Protocol Buffers for serialization.
Protocol Definition
The complete protobuf definition is available at proto/broker_gateway.proto.
Connection
use tonic::transport::Channel;
use broker_gateway::broker_gateway_service_client::BrokerGatewayServiceClient;
// Connect to service
let channel = Channel::from_static("http://localhost:50056")
.connect()
.await?;
let mut client = BrokerGatewayServiceClient::new(channel);
Authentication
MVP: No authentication (internal service)
Phase 2: mTLS client certificates + API keys
// Phase 2: TLS with client cert
use tonic::transport::{Certificate, ClientTlsConfig, Identity};
let cert = std::fs::read("client-cert.pem")?;
let key = std::fs::read("client-key.pem")?;
let identity = Identity::from_pem(cert, key);
let ca_cert = std::fs::read("ca-cert.pem")?;
let ca = Certificate::from_pem(ca_cert);
let tls = ClientTlsConfig::new()
.identity(identity)
.ca_certificate(ca)
.domain_name("broker-gateway.foxhunt.local");
let channel = Channel::from_static("https://broker-gateway:50056")
.tls_config(tls)?
.connect()
.await?;
let mut client = BrokerGatewayServiceClient::new(channel);
Service Methods
RouteOrder
Submit a new order to the broker.
Request
message RouteOrderRequest {
string symbol = 1; // ES, NQ, YM, RTY, etc.
OrderSide side = 2; // BUY or SELL
double quantity = 3; // Number of contracts (must be > 0)
OrderType order_type = 4; // MARKET, LIMIT, STOP, STOP_LIMIT
optional double price = 5; // Required for LIMIT orders
optional double stop_price = 6; // Required for STOP orders
string account_id = 7; // AMP account identifier
map<string, string> metadata = 8; // Optional metadata (strategy, model_name)
}
Response
message RouteOrderResponse {
string broker_order_id = 1; // Broker's OrderID (filled after ack)
string client_order_id = 2; // Our ClOrdID (UUID)
OrderStatus status = 3; // PENDING_SUBMIT, SUBMITTED, etc.
int64 submitted_at = 4; // Timestamp (nanoseconds)
string message = 5; // Success/error message
}
Example: Market Order
use broker_gateway::*;
let request = RouteOrderRequest {
symbol: "ES".to_string(),
side: OrderSide::Buy as i32,
quantity: 10.0,
order_type: OrderType::Market as i32,
price: None,
stop_price: None,
account_id: "ACCT_001".to_string(),
metadata: [
("strategy".to_string(), "momentum".to_string()),
("model".to_string(), "dqn_v2".to_string()),
]
.iter()
.cloned()
.collect(),
};
let response = client.route_order(request).await?;
let order = response.into_inner();
println!("Order submitted:");
println!(" Client Order ID: {}", order.client_order_id);
println!(" Status: {:?}", OrderStatus::try_from(order.status)?);
println!(" Submitted At: {}", order.submitted_at);
// Output:
// Order submitted:
// Client Order ID: 550e8400-e29b-41d4-a716-446655440000
// Status: PENDING_SUBMIT
// Submitted At: 1704812400000000000
Example: Limit Order
let request = RouteOrderRequest {
symbol: "NQ".to_string(),
side: OrderSide::Sell as i32,
quantity: 5.0,
order_type: OrderType::Limit as i32,
price: Some(18500.50), // Required for LIMIT
stop_price: None,
account_id: "ACCT_001".to_string(),
metadata: HashMap::new(),
};
let response = client.route_order(request).await?;
Example: Stop-Limit Order
let request = RouteOrderRequest {
symbol: "ES".to_string(),
side: OrderSide::Buy as i32,
quantity: 3.0,
order_type: OrderType::StopLimit as i32,
price: Some(5805.00), // Limit price (buy at 5805 after stop triggered)
stop_price: Some(5800.00), // Stop price (trigger at 5800)
account_id: "ACCT_001".to_string(),
metadata: HashMap::new(),
};
let response = client.route_order(request).await?;
Validation Rules
| Field | Validation |
|---|---|
symbol |
Non-empty, uppercase, valid futures contract |
quantity |
Must be > 0 |
price |
Required if order_type = LIMIT or STOP_LIMIT |
stop_price |
Required if order_type = STOP or STOP_LIMIT |
account_id |
Non-empty |
Error Codes
| Code | Description |
|---|---|
INVALID_ARGUMENT |
Missing required field or invalid value |
FAILED_PRECONDITION |
FIX session not active (Phase 2) |
RESOURCE_EXHAUSTED |
Rate limit exceeded |
INTERNAL |
Database error or unexpected failure |
CancelOrder
Cancel an existing order.
Request
message CancelOrderRequest {
string client_order_id = 1; // Order to cancel (required)
string account_id = 2; // Account verification (required)
}
Response
message CancelOrderResponse {
bool success = 1; // True if cancel request accepted
string message = 2; // Confirmation message or error
OrderStatus new_status = 3; // CANCEL_PENDING or CANCELLED
}
Example
let request = CancelOrderRequest {
client_order_id: "550e8400-e29b-41d4-a716-446655440000".to_string(),
account_id: "ACCT_001".to_string(),
};
let response = client.cancel_order(request).await?;
let cancel = response.into_inner();
if cancel.success {
println!("Cancel request accepted: {}", cancel.message);
println!("New status: {:?}", OrderStatus::try_from(cancel.new_status)?);
} else {
println!("Cancel request failed: {}", cancel.message);
}
// Output:
// Cancel request accepted: Cancel request queued (MVP: no FIX send). Order: 550e8400-...
// New status: CANCEL_PENDING
Cancellable States
Only orders in these states can be cancelled:
PENDING_SUBMITSUBMITTEDPARTIALLY_FILLED
Error Codes
| Code | Description |
|---|---|
NOT_FOUND |
Order not found or account_id mismatch |
FAILED_PRECONDITION |
Order already filled, cancelled, or rejected |
INTERNAL |
Database error |
GetAccountState
Retrieve current account balance and margin information.
Request
message GetAccountStateRequest {
string account_id = 1; // AMP account identifier
}
Response
message GetAccountStateResponse {
string account_id = 1;
double cash_balance = 2; // Cash balance (USD)
double equity = 3; // Cash + unrealized P&L
double margin_used = 4; // Margin locked by open positions
double margin_available = 5; // Available margin for new positions
double buying_power = 6; // Margin available * leverage
double unrealized_pnl = 7; // Unrealized profit/loss
double realized_pnl = 8; // Realized profit/loss (today)
int64 last_updated = 9; // Timestamp (nanoseconds)
}
Example
let request = GetAccountStateRequest {
account_id: "ACCT_001".to_string(),
};
let response = client.get_account_state(request).await?;
let state = response.into_inner();
println!("Account: {}", state.account_id);
println!("Cash Balance: ${:.2}", state.cash_balance);
println!("Equity: ${:.2}", state.equity);
println!("Margin Used: ${:.2}", state.margin_used);
println!("Margin Available: ${:.2}", state.margin_available);
println!("Buying Power: ${:.2}", state.buying_power);
println!("Unrealized P&L: ${:.2}", state.unrealized_pnl);
println!("Realized P&L: ${:.2}", state.realized_pnl);
// Output:
// Account: ACCT_001
// Cash Balance: $100000.00
// Equity: $100000.00
// Margin Used: $0.00
// Margin Available: $100000.00
// Buying Power: $400000.00
// Unrealized P&L: $0.00
// Realized P&L: $0.00
MVP Behavior
Returns placeholder data (cash_balance = $100,000, 4x leverage).
Phase 2: Queries CQG broker via FIX CollateralInquiry (MsgType=BB).
Error Codes
| Code | Description |
|---|---|
NOT_FOUND |
Account not found |
UNAVAILABLE |
Broker connection unavailable (Phase 2) |
INTERNAL |
Database error |
GetPositions
Retrieve current open positions.
Request
message GetPositionsRequest {
string account_id = 1; // AMP account identifier
optional string symbol = 2; // Filter by symbol (optional)
}
Response
message GetPositionsResponse {
repeated Position positions = 1; // List of positions
double total_equity = 2; // Total account equity
double total_exposure = 3; // Sum of abs(position_value)
double leverage_ratio = 4; // total_exposure / total_equity
int64 timestamp = 5; // Timestamp (nanoseconds)
}
message Position {
string symbol = 1; // ES, NQ, etc.
double quantity = 2; // Positive = long, negative = short
double average_price = 3; // Average entry price
double market_value = 4; // quantity * current_price
double unrealized_pnl = 5; // (current_price - avg_price) * quantity
}
Example: All Positions
let request = GetPositionsRequest {
account_id: "ACCT_001".to_string(),
symbol: None, // All symbols
};
let response = client.get_positions(request).await?;
let positions_response = response.into_inner();
println!("Total Equity: ${:.2}", positions_response.total_equity);
println!("Total Exposure: ${:.2}", positions_response.total_exposure);
println!("Leverage: {:.2}x", positions_response.leverage_ratio);
println!("\nPositions:");
for pos in &positions_response.positions {
println!(" {} x {} @ ${:.2} | Market: ${:.2} | P&L: ${:.2}",
pos.symbol,
pos.quantity,
pos.average_price,
pos.market_value,
pos.unrealized_pnl
);
}
// Output:
// Total Equity: $102350.00
// Total Exposure: $290000.00
// Leverage: 2.83x
//
// Positions:
// ES x 10 @ $5800.00 | Market: $58050.00 | P&L: $250.00
// NQ x -5 @ $18500.00 | Market: $-92000.00 | P&L: $-100.00
Example: Single Symbol
let request = GetPositionsRequest {
account_id: "ACCT_001".to_string(),
symbol: Some("ES".to_string()),
};
let response = client.get_positions(request).await?;
// Returns only ES positions
MVP Behavior
Returns empty positions list.
Phase 2: Queries CQG broker via FIX RequestForPositions (MsgType=AN).
Error Codes
| Code | Description |
|---|---|
NOT_FOUND |
Account not found |
UNAVAILABLE |
Broker connection unavailable (Phase 2) |
INTERNAL |
Database error |
GetSessionStatus
Retrieve FIX session status and health metrics.
Request
message GetSessionStatusRequest {
optional string session_id = 1; // Optional: default to active session
}
Response
message GetSessionStatusResponse {
string session_id = 1; // Session identifier (e.g., "FOXHUNT-CQG")
SessionState state = 2; // DISCONNECTED, CONNECTED, ACTIVE, etc.
int64 sender_seq_num = 3; // Current outgoing MsgSeqNum
int64 target_seq_num = 4; // Expected incoming MsgSeqNum
int64 last_heartbeat_sent = 5; // Timestamp (nanoseconds)
int64 last_heartbeat_received = 6; // Timestamp (nanoseconds)
double heartbeat_rtt_ms = 7; // Round-trip time (milliseconds)
int64 connected_at = 8; // Connection timestamp (nanoseconds)
map<string, string> details = 9; // Additional info
}
enum SessionState {
SESSION_STATE_DISCONNECTED = 0;
SESSION_STATE_CONNECTED = 1;
SESSION_STATE_LOGGING_IN = 2;
SESSION_STATE_ACTIVE = 3;
SESSION_STATE_LOGGING_OUT = 4;
}
Example
let request = GetSessionStatusRequest {
session_id: None, // Use default session
};
let response = client.get_session_status(request).await?;
let status = response.into_inner();
println!("Session: {}", status.session_id);
println!("State: {:?}", SessionState::try_from(status.state)?);
println!("Sender Seq: {}", status.sender_seq_num);
println!("Target Seq: {}", status.target_seq_num);
println!("Heartbeat RTT: {:.2} ms", status.heartbeat_rtt_ms);
// Output:
// Session: FOXHUNT-CQG
// State: ACTIVE
// Sender Seq: 5432
// Target Seq: 5398
// Heartbeat RTT: 23.45 ms
MVP Behavior
Returns simulated session state (ACTIVE, sequence = 1).
Phase 2: Returns actual FIX session metrics.
Error Codes
| Code | Description |
|---|---|
NOT_FOUND |
Session ID not found |
INTERNAL |
Database error |
StreamExecutions
Stream real-time execution reports from the broker.
Request
message StreamExecutionsRequest {
optional string account_id = 1; // Filter by account (optional)
optional string symbol = 2; // Filter by symbol (optional)
}
Response (Stream)
message ExecutionEvent {
string execution_id = 1; // ExecID (Tag 17)
string broker_order_id = 2; // OrderID (Tag 37)
string client_order_id = 3; // ClOrdID (Tag 11)
string symbol = 4;
OrderSide side = 5;
ExecutionType exec_type = 6; // NEW, TRADE, CANCELED, REJECTED
OrderStatus order_status = 7; // Order status after this execution
double last_qty = 8; // Quantity filled (Tag 32)
double last_price = 9; // Fill price (Tag 31)
double cum_qty = 10; // Total filled (Tag 14)
double avg_price = 11; // Average fill price (Tag 6)
int64 transact_time = 12; // Execution timestamp
optional string text = 13; // Reject reason (if applicable)
}
enum ExecutionType {
EXECUTION_TYPE_UNSPECIFIED = 0;
EXECUTION_TYPE_NEW = 1; // Order accepted by broker
EXECUTION_TYPE_TRADE = 2; // Partial or full fill
EXECUTION_TYPE_CANCELED = 3; // Order canceled
EXECUTION_TYPE_REJECTED = 4; // Order rejected
}
Example
use tokio_stream::StreamExt;
let request = StreamExecutionsRequest {
account_id: Some("ACCT_001".to_string()),
symbol: None, // All symbols
};
let mut stream = client.stream_executions(request).await?.into_inner();
println!("Streaming executions...");
while let Some(event) = stream.next().await {
match event {
Ok(exec) => {
println!("\n--- Execution Event ---");
println!("Execution ID: {}", exec.execution_id);
println!("Order ID: {} (Broker: {})", exec.client_order_id, exec.broker_order_id);
println!("Symbol: {} {} {}", exec.symbol,
if exec.side == OrderSide::Buy as i32 { "BUY" } else { "SELL" },
exec.last_qty);
println!("Exec Type: {:?}", ExecutionType::try_from(exec.exec_type)?);
println!("Order Status: {:?}", OrderStatus::try_from(exec.order_status)?);
if exec.exec_type == ExecutionType::Trade as i32 {
println!("Fill: {} @ ${:.2}", exec.last_qty, exec.last_price);
println!("Cumulative: {} @ ${:.2} avg", exec.cum_qty, exec.avg_price);
}
if let Some(text) = exec.text {
println!("Message: {}", text);
}
}
Err(e) => {
eprintln!("Stream error: {}", e);
break;
}
}
}
// Output:
// Streaming executions...
//
// --- Execution Event ---
// Execution ID: EXEC_789
// Order ID: 550e8400-... (Broker: BROKER_123)
// Symbol: ES BUY 10
// Exec Type: TRADE
// Order Status: FILLED
// Fill: 10 @ $5800.25
// Cumulative: 10 @ $5800.25 avg
MVP Behavior
Stream closes immediately (no executions).
Phase 2: Streams real-time FIX ExecutionReport messages.
Error Codes
| Code | Description |
|---|---|
UNAVAILABLE |
FIX session not active (Phase 2) |
INTERNAL |
Stream initialization failed |
HealthCheck
Check service health (database, FIX session).
Request
message HealthCheckRequest {}
Response
message HealthCheckResponse {
bool healthy = 1; // True if service is healthy
string message = 2; // Status message
map<string, string> details = 3; // Component health details
}
Example
let request = HealthCheckRequest {};
let response = client.health_check(request).await?;
let health = response.into_inner();
println!("Service Healthy: {}", health.healthy);
println!("Message: {}", health.message);
println!("Details:");
for (key, value) in &health.details {
println!(" {}: {}", key, value);
}
// Output:
// Service Healthy: true
// Message: Broker Gateway Service is healthy (MVP mode)
// Details:
// database: true
// mvp_mode: true
// fix_session: not_implemented
Error Codes
Always returns OK. Check healthy field in response.
Data Types
Enums
OrderSide
enum OrderSide {
ORDER_SIDE_UNSPECIFIED = 0;
ORDER_SIDE_BUY = 1;
ORDER_SIDE_SELL = 2;
}
OrderType
enum OrderType {
ORDER_TYPE_UNSPECIFIED = 0;
ORDER_TYPE_MARKET = 1;
ORDER_TYPE_LIMIT = 2;
ORDER_TYPE_STOP = 3;
ORDER_TYPE_STOP_LIMIT = 4;
}
OrderStatus
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_PENDING_SUBMIT = 1; // Order created, not sent
ORDER_STATUS_SUBMITTED = 2; // Sent to broker
ORDER_STATUS_PARTIALLY_FILLED = 3; // Partially filled
ORDER_STATUS_FILLED = 4; // Fully filled
ORDER_STATUS_CANCEL_PENDING = 5; // Cancel request sent
ORDER_STATUS_CANCELLED = 6; // Cancelled by broker
ORDER_STATUS_REJECTED = 7; // Rejected by broker
}
ExecutionType
enum ExecutionType {
EXECUTION_TYPE_UNSPECIFIED = 0;
EXECUTION_TYPE_NEW = 1; // Order accepted
EXECUTION_TYPE_TRADE = 2; // Fill (partial or full)
EXECUTION_TYPE_CANCELED = 3; // Order canceled
EXECUTION_TYPE_REJECTED = 4; // Order rejected
}
SessionState
enum SessionState {
SESSION_STATE_DISCONNECTED = 0;
SESSION_STATE_CONNECTED = 1;
SESSION_STATE_LOGGING_IN = 2;
SESSION_STATE_ACTIVE = 3;
SESSION_STATE_LOGGING_OUT = 4;
}
Error Codes
gRPC Status Codes
| Code | HTTP | Description | Retry |
|---|---|---|---|
OK |
200 | Success | - |
INVALID_ARGUMENT |
400 | Invalid request parameters | No |
NOT_FOUND |
404 | Resource not found | No |
ALREADY_EXISTS |
409 | Duplicate order ID | No |
FAILED_PRECONDITION |
400 | Order not cancellable | No |
RESOURCE_EXHAUSTED |
429 | Rate limit exceeded | Yes (backoff) |
UNAVAILABLE |
503 | Service unavailable | Yes (backoff) |
INTERNAL |
500 | Internal server error | Yes (limited) |
DEADLINE_EXCEEDED |
504 | Request timeout | Yes (once) |
Error Details
Errors include structured details in metadata:
use tonic::{Code, Status};
// Example error response
let status = Status::new(
Code::InvalidArgument,
"Price is required for LIMIT orders"
);
// Client error handling
match client.route_order(request).await {
Ok(response) => { /* ... */ },
Err(e) => {
match e.code() {
Code::InvalidArgument => {
eprintln!("Validation error: {}", e.message());
// Don't retry
}
Code::Unavailable => {
eprintln!("Service unavailable: {}", e.message());
// Retry with backoff
}
_ => {
eprintln!("Unexpected error: {} ({})", e.message(), e.code());
}
}
}
}
Rate Limits
MVP
No rate limits.
Phase 2
| Method | Limit | Window |
|---|---|---|
| RouteOrder | 100 req/sec | Per account |
| CancelOrder | 50 req/sec | Per account |
| GetAccountState | 10 req/sec | Per account |
| GetPositions | 10 req/sec | Per account |
| GetSessionStatus | 5 req/sec | Global |
| StreamExecutions | 1 connection | Per account |
Rate limit exceeded returns RESOURCE_EXHAUSTED (HTTP 429).
Client Examples
Complete Order Lifecycle
use broker_gateway::broker_gateway_service_client::BrokerGatewayServiceClient;
use broker_gateway::*;
use tonic::transport::Channel;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Connect to service
let channel = Channel::from_static("http://localhost:50056")
.connect()
.await?;
let mut client = BrokerGatewayServiceClient::new(channel);
// 2. Stream executions in background
let mut stream_client = client.clone();
tokio::spawn(async move {
let request = StreamExecutionsRequest {
account_id: Some("ACCT_001".to_string()),
symbol: None,
};
let mut stream = stream_client.stream_executions(request).await.unwrap().into_inner();
while let Some(event) = stream.next().await {
if let Ok(exec) = event {
println!("Execution: {} {} @ ${}", exec.symbol, exec.last_qty, exec.last_price);
}
}
});
// 3. Submit market order
let request = RouteOrderRequest {
symbol: "ES".to_string(),
side: OrderSide::Buy as i32,
quantity: 10.0,
order_type: OrderType::Market as i32,
price: None,
stop_price: None,
account_id: "ACCT_001".to_string(),
metadata: HashMap::new(),
};
let response = client.route_order(request).await?;
let order = response.into_inner();
println!("Order submitted: {}", order.client_order_id);
// 4. Wait 5 seconds (simulating fill delay)
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
// 5. Get updated positions
let request = GetPositionsRequest {
account_id: "ACCT_001".to_string(),
symbol: Some("ES".to_string()),
};
let response = client.get_positions(request).await?;
let positions = response.into_inner();
for pos in positions.positions {
println!("Position: {} x {}", pos.symbol, pos.quantity);
}
Ok(())
}
Error Handling with Retries
use std::time::Duration;
use tokio::time::sleep;
async fn route_order_with_retry(
client: &mut BrokerGatewayServiceClient<Channel>,
request: RouteOrderRequest,
max_retries: u32,
) -> Result<RouteOrderResponse, tonic::Status> {
let mut attempt = 0;
let mut delay = Duration::from_millis(100);
loop {
match client.route_order(request.clone()).await {
Ok(response) => return Ok(response.into_inner()),
Err(e) => {
let should_retry = matches!(
e.code(),
Code::Unavailable | Code::DeadlineExceeded | Code::ResourceExhausted
);
if should_retry && attempt < max_retries {
println!("Retry attempt {}/{} after {:?}", attempt + 1, max_retries, delay);
sleep(delay).await;
attempt += 1;
delay = Duration::min(delay * 2, Duration::from_secs(10));
} else {
return Err(e);
}
}
}
}
}
Bulk Order Submission
async fn submit_bulk_orders(
client: &mut BrokerGatewayServiceClient<Channel>,
orders: Vec<RouteOrderRequest>,
) -> Vec<Result<String, String>> {
let mut results = Vec::new();
for order in orders {
let result = match client.route_order(order.clone()).await {
Ok(response) => Ok(response.into_inner().client_order_id),
Err(e) => Err(e.message().to_string()),
};
results.push(result);
}
results
}
// Example usage
let orders = vec![
RouteOrderRequest { symbol: "ES".to_string(), /* ... */ },
RouteOrderRequest { symbol: "NQ".to_string(), /* ... */ },
RouteOrderRequest { symbol: "YM".to_string(), /* ... */ },
];
let results = submit_bulk_orders(&mut client, orders).await;
for (i, result) in results.iter().enumerate() {
match result {
Ok(order_id) => println!("Order {} submitted: {}", i, order_id),
Err(e) => println!("Order {} failed: {}", i, e),
}
}
Changelog
v0.1.0 (2025-01-09)
MVP Release:
- gRPC API endpoints (7 methods)
- Database persistence
- Request validation
- Health checks
Limitations:
- No FIX protocol (placeholder responses)
- No real broker connectivity
- StreamExecutions closes immediately
v0.2.0 (Planned - Phase 2)
Full FIX Integration:
- FIX 4.2/4.4 protocol implementation
- CQG broker connectivity
- Real-time execution streaming
- Position reconciliation
- Session management (LOGON, LOGOUT, Heartbeat)
- Sequence number tracking
Support
For issues or questions:
- Internal: Slack #broker-gateway-support
- Email: ops@foxhunt.trading
- Runbook: See TROUBLESHOOTING.md