Files
foxhunt/tests/unit/unit-tests-src/execution.rs
jgrusewski c8c58f24c2 🚀 MAJOR FIX: Parallel agents eliminate 330+ compilation errors
- Fixed all FromPrimitive imports across codebase
- Resolved all common::types import paths (219+ files)
- Fixed Volume constructor issues (type alias vs struct)
- Resolved all E0308 type mismatches
- Fixed ExecutionReport and BrokerError imports
- Added missing Price arithmetic assignment traits
- Fixed Decimal to_f64 method calls with ToPrimitive
- Eliminated all re-exports per architectural rules

Errors reduced from 436 to 106 - 76% reduction achieved
2025-09-26 20:36:21 +02:00

898 lines
34 KiB
Rust

//! Order execution unit tests
//!
//! Tests order routing, execution algorithms, and venue selection
//! with focus on best execution and latency optimization.
use common::*;
// CANONICAL TYPE IMPORTS - Use types::prelude::Decimal
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_smart_order_router_venue_selection() {
let mut router = SmartOrderRouter::new();
// Add venues with different characteristics
router.add_venue(Venue {
id: "NYSE".to_string(),
latency: Duration::from_millis(2),
fee_rate: Decimal::from_str("0.0005").unwrap(), // 0.05%
liquidity_score: 95,
reliability_score: 99,
});
router.add_venue(Venue {
id: "NASDAQ".to_string(),
latency: Duration::from_millis(1),
fee_rate: Decimal::from_str("0.0007").unwrap(), // 0.07%
liquidity_score: 90,
reliability_score: 98,
});
router.add_venue(Venue {
id: "DARK_POOL".to_string(),
latency: Duration::from_millis(5),
fee_rate: Decimal::from_str("0.0002").unwrap(), // 0.02%
liquidity_score: 70,
reliability_score: 95,
});
// Small order - should prefer low latency
let small_order = Order {
id: OrderId::from("SMALL-001"),
symbol: Symbol::new("AAPL".to_string()).unwrap(),
side: Side::Buy,
order_type: OrderType::Market,
quantity: Quantity::new(Decimal::from(100)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(),
price: None,
time_in_force: TimeInForce::IOC,
timestamp: chrono::Utc::now(),
status: OrderStatus::New,
client_id: ClientId::new("CLIENT-001".to_string()),
};
let small_routing = router.select_venue(&small_order, VenueSelectionStrategy::LatencyOptimized).await
.expect("Should select venue for small order");
assert_eq!(small_routing.venue_id, "NASDAQ"); // Lowest latency
// Large order - should prefer liquidity
let large_order = Order {
id: OrderId::from("LARGE-001"),
symbol: Symbol::new("AAPL".to_string()).unwrap(),
side: Side::Buy,
order_type: OrderType::Limit,
quantity: Quantity::new(Decimal::from(10000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(),
price: Some(Price::new(Decimal::from(150)).unwrap()),
time_in_force: TimeInForce::Day,
timestamp: chrono::Utc::now(),
status: OrderStatus::New,
client_id: ClientId::new("CLIENT-001".to_string()),
};
let large_routing = router.select_venue(&large_order, VenueSelectionStrategy::LiquidityOptimized).await
.expect("Should select venue for large order");
assert_eq!(large_routing.venue_id, "NYSE"); // Highest liquidity
}
#[tokio::test]
async fn test_twap_execution_algorithm() {
let mut twap = TwapExecutor::new(
Duration::from_secs(300), // 5 minutes
20 // 20 child orders
);
let parent_order = Order {
id: OrderId::from("TWAP-PARENT"),
symbol: Symbol::new("MSFT".to_string()).unwrap(),
side: Side::Buy,
order_type: OrderType::Limit,
quantity: Quantity::new(Decimal::from(10000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(),
price: Some(Price::new(Decimal::from(300)).unwrap()),
time_in_force: TimeInForce::Day,
timestamp: chrono::Utc::now(),
status: OrderStatus::New,
client_id: ClientId::new("INST-001".to_string()),
};
let execution_plan = twap.create_execution_plan(&parent_order).await
.expect("Should create TWAP execution plan");
// Should create 20 child orders
assert_eq!(execution_plan.child_orders.len(), 20);
// Each child order should be 500 shares (10000 / 20)
for child_order in &execution_plan.child_orders {
assert_eq!(child_order.quantity.value(), Decimal::from(500));
}
// Should have proper timing intervals (15 seconds each)
let expected_interval = Duration::from_secs(15); // 300 seconds / 20 orders
for i in 1..execution_plan.execution_schedule.len() {
let interval = execution_plan.execution_schedule[i] - execution_plan.execution_schedule[i-1];
let tolerance = Duration::from_millis(100);
assert!((interval - expected_interval) < tolerance);
}
}
#[tokio::test]
async fn test_vwap_execution_algorithm() {
let mut vwap = VwapExecutor::new();
// Mock historical volume profile
let volume_profile = vec![
(chrono::NaiveTime::from_hms_opt(9, 30, 0).unwrap(), Decimal::from(1000)), // Market open
(chrono::NaiveTime::from_hms_opt(10, 0, 0).unwrap(), Decimal::from(800)),
(chrono::NaiveTime::from_hms_opt(11, 0, 0).unwrap(), Decimal::from(600)),
(chrono::NaiveTime::from_hms_opt(14, 0, 0).unwrap(), Decimal::from(700)), // Lunch time
(chrono::NaiveTime::from_hms_opt(15, 30, 0).unwrap(), Decimal::from(1200)), // Market close
];
vwap.set_volume_profile(Symbol::new("GOOGL".to_string()).unwrap(), volume_profile);
let parent_order = Order {
id: OrderId::from("VWAP-PARENT"),
symbol: Symbol::new("GOOGL".to_string()).unwrap(),
side: Side::Sell,
order_type: OrderType::Limit,
quantity: Quantity::new(Decimal::from(5000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(),
price: Some(Price::new(Decimal::from(2500)).unwrap()),
time_in_force: TimeInForce::Day,
timestamp: chrono::Utc::now(),
status: OrderStatus::New,
client_id: ClientId::new("FUND-001".to_string()),
};
let execution_plan = vwap.create_execution_plan(&parent_order).await
.expect("Should create VWAP execution plan");
// Should weight execution based on historical volume
// Market open and close should have larger orders
let open_order = execution_plan.child_orders.iter()
.find(|o| o.id.value().contains("09:30"))
.expect("Should have market open order");
let close_order = execution_plan.child_orders.iter()
.find(|o| o.id.value().contains("15:30"))
.expect("Should have market close order");
let midday_order = execution_plan.child_orders.iter()
.find(|o| o.id.value().contains("11:00"))
.expect("Should have midday order");
// Open and close orders should be larger than midday
assert!(open_order.quantity.value() > midday_order.quantity.value());
assert!(close_order.quantity.value() > midday_order.quantity.value());
}
#[tokio::test]
async fn test_implementation_shortfall_execution() {
let mut is_executor = ImplementationShortfallExecutor::new(
Decimal::from_str("0.02").unwrap(), // 2% risk aversion
Duration::from_secs(1800) // 30 minutes
);
let parent_order = Order {
id: OrderId::from("IS-PARENT"),
symbol: Symbol::new("TSLA".to_string()).unwrap(),
side: Side::Buy,
order_type: OrderType::Market,
quantity: Quantity::new(Decimal::from(2000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(),
price: None,
time_in_force: TimeInForce::Day,
timestamp: chrono::Utc::now(),
status: OrderStatus::New,
client_id: ClientId::new("HEDGE-001".to_string()),
};
// Set market conditions
let market_conditions = MarketConditions {
volatility: Decimal::from_str("0.25").unwrap(), // 25% annualized
spread: Decimal::from_str("0.10").unwrap(), // $0.10 spread
temporary_impact_rate: Decimal::from_str("0.001").unwrap(),
permanent_impact_rate: Decimal::from_str("0.0005").unwrap(),
};
is_executor.set_market_conditions(market_conditions);
let execution_plan = is_executor.create_execution_plan(&parent_order).await
.expect("Should create IS execution plan");
// Should optimize trade-off between market impact and timing risk
assert!(!execution_plan.child_orders.is_empty());
// In high volatility, should execute faster to reduce timing risk
let total_execution_time = execution_plan.execution_schedule.last().unwrap() -
execution_plan.execution_schedule.first().unwrap();
assert!(total_execution_time < Duration::from_secs(1800)); // Less than max time
assert!(total_execution_time > Duration::from_secs(60)); // But not too fast
}
#[tokio::test]
async fn test_iceberg_order_execution() {
let mut iceberg = IcebergExecutor::new(
Quantity::new(Decimal::from(500)).map_err(|e| format!("Failed to create iceberg quantity: {}", e)).unwrap(), // Show 500 shares at a time
Decimal::from_str("0.01").unwrap() // 1% price improvement threshold
);
let parent_order = Order {
id: OrderId::from("ICEBERG-PARENT"),
symbol: Symbol::new("AMZN".to_string()).unwrap(),
side: Side::Buy,
order_type: OrderType::Limit,
quantity: Quantity::new(Decimal::from(5000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(),
price: Some(Price::new(Decimal::from(3000)).unwrap()),
time_in_force: TimeInForce::Day,
timestamp: chrono::Utc::now(),
status: OrderStatus::New,
client_id: ClientId::new("CLIENT-001".to_string()),
};
let initial_slice = iceberg.get_initial_slice(&parent_order).await
.expect("Should get initial iceberg slice");
// Initial slice should be clip size
assert_eq!(initial_slice.quantity.value(), Decimal::from(500));
assert_eq!(initial_slice.price, parent_order.price);
// Simulate partial fill
let fill = Fill {
id: FillId::new("FILL-ICE-001".to_string()),
order_id: initial_slice.id.clone(),
trade_id: TradeId::new("TRADE-ICE-001".to_string()),
symbol: parent_order.symbol.clone(),
side: parent_order.side,
quantity: Quantity::new(Decimal::from(300)).map_err(|e| format!("Failed to create fill quantity: {}", e)).unwrap(),
price: Price::new(Decimal::from(3000)).unwrap(),
timestamp: chrono::Utc::now(),
commission: None,
};
let next_slice = iceberg.handle_fill(&parent_order, &fill).await
.expect("Should handle iceberg fill");
assert!(next_slice.is_some());
let next = next_slice.unwrap();
// Should replenish to show full clip size again
assert_eq!(next.quantity.value(), Decimal::from(500)); // 200 remaining + 300 new
}
#[tokio::test]
async fn test_execution_quality_measurement() {
let mut quality_tracker = ExecutionQualityTracker::new();
// Record executions with different quality metrics
let execution1 = ExecutionResult {
order_id: OrderId::from("EXEC-001"),
symbol: Symbol::new("AAPL".to_string()).unwrap(),
expected_price: Price::new(Decimal::from(150)).unwrap(),
executed_price: Price::new(Decimal::from_str("150.05").unwrap()).unwrap(),
quantity: Quantity::new(Decimal::from(1000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(),
execution_time: Duration::from_millis(50),
venue: "NYSE".to_string(),
timestamp: chrono::Utc::now(),
};
let execution2 = ExecutionResult {
order_id: OrderId::from("EXEC-002"),
symbol: Symbol::new("AAPL".to_string()).unwrap(),
expected_price: Price::new(Decimal::from(150)).unwrap(),
executed_price: Price::new(Decimal::from_str("149.98").unwrap()).unwrap(),
quantity: Quantity::new(Decimal::from(500)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(),
execution_time: Duration::from_millis(25),
venue: "NASDAQ".to_string(),
timestamp: chrono::Utc::now(),
};
quality_tracker.record_execution(execution1);
quality_tracker.record_execution(execution2);
let quality_metrics = quality_tracker.calculate_metrics(Duration::from_secs(3600)).await
.expect("Should calculate quality metrics");
// Should calculate average slippage
let expected_avg_slippage = (Decimal::from_str("0.05").unwrap() + Decimal::from_str("-0.02").unwrap()) / Decimal::from(2);
assert_eq!(quality_metrics.average_slippage, expected_avg_slippage);
// Should track fill rates and timing
assert_eq!(quality_metrics.total_executions, 2);
assert!(quality_metrics.average_execution_time < Duration::from_millis(50));
}
#[tokio::test]
async fn test_multi_venue_execution() {
let mut multi_venue = MultiVenueExecutor::new();
multi_venue.add_venue("NYSE", 0.3); // 30% allocation
multi_venue.add_venue("NASDAQ", 0.4); // 40% allocation
multi_venue.add_venue("BATS", 0.2); // 20% allocation
multi_venue.add_venue("IEX", 0.1); // 10% allocation
let large_order = Order {
id: OrderId::from("MULTI-VENUE"),
symbol: Symbol::new("SPY".to_string()).unwrap(),
side: Side::Buy,
order_type: OrderType::Limit,
quantity: Quantity::new(Decimal::from(10000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(),
price: Some(Price::new(Decimal::from(400)).unwrap()),
time_in_force: TimeInForce::Day,
timestamp: chrono::Utc::now(),
status: OrderStatus::New,
client_id: ClientId::new("INSTITUTION".to_string()),
};
let venue_orders = multi_venue.split_order(&large_order).await
.expect("Should split order across venues");
assert_eq!(venue_orders.len(), 4);
// Check allocations
let nyse_order = venue_orders.iter().find(|o| o.venue_id == "NYSE").unwrap();
let nasdaq_order = venue_orders.iter().find(|o| o.venue_id == "NASDAQ").unwrap();
let bats_order = venue_orders.iter().find(|o| o.venue_id == "BATS").unwrap();
let iex_order = venue_orders.iter().find(|o| o.venue_id == "IEX").unwrap();
assert_eq!(nyse_order.quantity.value(), Decimal::from(3000)); // 30%
assert_eq!(nasdaq_order.quantity.value(), Decimal::from(4000)); // 40%
assert_eq!(bats_order.quantity.value(), Decimal::from(2000)); // 20%
assert_eq!(iex_order.quantity.value(), Decimal::from(1000)); // 10%
// Total should match original order
let total_quantity: Decimal = venue_orders.iter()
.map(|o| o.quantity.value())
.sum();
assert_eq!(total_quantity, large_order.quantity.value());
}
#[tokio::test]
async fn test_latency_measurement() {
let mut latency_tracker = LatencyTracker::new();
let start_time = Instant::now();
// Simulate order lifecycle with timing points
let order_id = OrderId::from("LATENCY-TEST");
latency_tracker.mark_order_sent(&order_id, start_time);
let ack_time = start_time + Duration::from_micros(500); // 500μs to ack
latency_tracker.mark_order_ack(&order_id, ack_time);
let fill_time = start_time + Duration::from_millis(2); // 2ms to fill
latency_tracker.mark_order_fill(&order_id, fill_time);
let metrics = latency_tracker.get_metrics(&order_id).await
.expect("Should get latency metrics");
assert_eq!(metrics.order_to_ack, Duration::from_micros(500));
assert_eq!(metrics.order_to_fill, Duration::from_millis(2));
assert_eq!(metrics.ack_to_fill, Duration::from_millis(2) - Duration::from_micros(500));
}
}
// Production implementations for testing
#[derive(Debug, Clone)]
struct Venue {
id: String,
latency: Duration,
fee_rate: Decimal,
liquidity_score: u32,
reliability_score: u32,
}
#[derive(Debug)]
struct SmartOrderRouter {
venues: Vec<Venue>,
}
#[derive(Debug)]
enum VenueSelectionStrategy {
LatencyOptimized,
LiquidityOptimized,
CostOptimized,
Balanced,
}
#[derive(Debug)]
struct VenueRoutingDecision {
venue_id: String,
expected_latency: Duration,
expected_fee: Money,
confidence_score: u32,
}
impl SmartOrderRouter {
fn new() -> Self {
Self {
venues: Vec::new(),
}
}
fn add_venue(&mut self, venue: Venue) {
self.venues.push(venue);
}
async fn select_venue(&self, order: &Order, strategy: VenueSelectionStrategy) -> Result<VenueRoutingDecision, Box<dyn std::error::Error>> {
if self.venues.is_empty() {
return Err("No venues available".into());
}
let best_venue = match strategy {
VenueSelectionStrategy::LatencyOptimized => {
self.venues.iter().min_by_key(|v| v.latency).unwrap()
},
VenueSelectionStrategy::LiquidityOptimized => {
self.venues.iter().max_by_key(|v| v.liquidity_score).unwrap()
},
VenueSelectionStrategy::CostOptimized => {
self.venues.iter().min_by_key(|v| v.fee_rate).unwrap()
},
VenueSelectionStrategy::Balanced => {
// Simple scoring: combine latency, liquidity, and cost
self.venues.iter().max_by_key(|v| {
let latency_score = 100 - (v.latency.as_millis() as u32);
let cost_score = 100 - (v.fee_rate * Decimal::from(10000)).to_u32().unwrap_or(0);
latency_score + v.liquidity_score + cost_score + v.reliability_score
}).unwrap()
}
};
let order_value = order.quantity.value() *
order.price.as_ref().unwrap_or(&Price::new(Decimal::from(100)).unwrap()).value();
Ok(VenueRoutingDecision {
venue_id: best_venue.id.clone(),
expected_latency: best_venue.latency,
expected_fee: Money::new(order_value * best_venue.fee_rate, Currency::USD),
confidence_score: best_venue.reliability_score,
})
}
}
#[derive(Debug)]
struct TwapExecutor {
duration: Duration,
num_slices: usize,
}
#[derive(Debug)]
struct ExecutionPlan {
child_orders: Vec<Order>,
execution_schedule: Vec<chrono::DateTime<chrono::Utc>>,
}
impl TwapExecutor {
fn new(duration: Duration, num_slices: usize) -> Self {
Self { duration, num_slices }
}
async fn create_execution_plan(&self, parent_order: &Order) -> Result<ExecutionPlan, Box<dyn std::error::Error>> {
let slice_size = parent_order.quantity.value() / Decimal::from(self.num_slices);
let slice_interval = self.duration / self.num_slices as u32;
let mut child_orders = Vec::new();
let mut execution_schedule = Vec::new();
let start_time = chrono::Utc::now();
for i in 0..self.num_slices {
let child_order = Order {
id: OrderId::new(format!("{}-TWAP-{}", parent_order.id.value(), i)),
symbol: parent_order.symbol.clone(),
side: parent_order.side,
order_type: parent_order.order_type,
quantity: Quantity::new(slice_size).map_err(|e| format!("Failed to create slice quantity: {}", e)).unwrap(),
price: parent_order.price.clone(),
time_in_force: TimeInForce::IOC,
timestamp: parent_order.timestamp,
status: OrderStatus::New,
client_id: parent_order.client_id.clone(),
};
child_orders.push(child_order);
execution_schedule.push(start_time + chrono::Duration::from_std(slice_interval * i as u32).unwrap());
}
Ok(ExecutionPlan {
child_orders,
execution_schedule,
})
}
}
#[derive(Debug)]
struct VwapExecutor {
volume_profiles: HashMap<Symbol, Vec<(chrono::NaiveTime, Decimal)>>,
}
impl VwapExecutor {
fn new() -> Self {
Self {
volume_profiles: HashMap::new(),
}
}
fn set_volume_profile(&mut self, symbol: Symbol, profile: Vec<(chrono::NaiveTime, Decimal)>) {
self.volume_profiles.insert(symbol, profile);
}
async fn create_execution_plan(&self, parent_order: &Order) -> Result<ExecutionPlan, Box<dyn std::error::Error>> {
let profile = self.volume_profiles.get(&parent_order.symbol)
.ok_or("No volume profile for symbol")?;
let total_volume: Decimal = profile.iter().map(|(_, vol)| *vol).sum();
let mut child_orders = Vec::new();
let mut execution_schedule = Vec::new();
let base_date = chrono::Utc::now().date_naive();
for (time, historical_volume) in profile {
let weight = historical_volume / total_volume;
let slice_quantity = parent_order.quantity.value() * weight;
if slice_quantity > Decimal::ZERO {
let child_order = Order {
id: OrderId::new(format!("{}-VWAP-{}", parent_order.id.value(), time.format("%H:%M"))),
symbol: parent_order.symbol.clone(),
side: parent_order.side,
order_type: parent_order.order_type,
quantity: Quantity::new(slice_quantity).map_err(|e| format!("Failed to create slice quantity: {}", e)).unwrap(),
price: parent_order.price.clone(),
time_in_force: TimeInForce::IOC,
timestamp: parent_order.timestamp,
status: OrderStatus::New,
client_id: parent_order.client_id.clone(),
};
child_orders.push(child_order);
let execution_time = chrono::DateTime::from_naive_utc_and_offset(
base_date.and_time(*time),
chrono::Utc
);
execution_schedule.push(execution_time);
}
}
Ok(ExecutionPlan {
child_orders,
execution_schedule,
})
}
}
#[derive(Debug)]
struct ImplementationShortfallExecutor {
risk_aversion: Decimal,
max_duration: Duration,
}
#[derive(Debug)]
struct MarketConditions {
volatility: Decimal,
spread: Decimal,
temporary_impact_rate: Decimal,
permanent_impact_rate: Decimal,
}
impl ImplementationShortfallExecutor {
fn new(risk_aversion: Decimal, max_duration: Duration) -> Self {
Self { risk_aversion, max_duration }
}
fn set_market_conditions(&mut self, _conditions: MarketConditions) {
// Store market conditions for optimization
}
async fn create_execution_plan(&self, parent_order: &Order) -> Result<ExecutionPlan, Box<dyn std::error::Error>> {
// Simplified IS optimization - in practice would solve for optimal trajectory
let num_slices = if parent_order.quantity.value() > Decimal::from(5000) { 10 } else { 5 };
let execution_duration = self.max_duration / 2; // Execute faster in volatile markets
let slice_size = parent_order.quantity.value() / Decimal::from(num_slices);
let slice_interval = execution_duration / num_slices as u32;
let mut child_orders = Vec::new();
let mut execution_schedule = Vec::new();
let start_time = chrono::Utc::now();
for i in 0..num_slices {
let child_order = Order {
id: OrderId::new(format!("{}-IS-{}", parent_order.id.value(), i)),
symbol: parent_order.symbol.clone(),
side: parent_order.side,
order_type: OrderType::Limit, // Use limit orders for better control
quantity: Quantity::new(slice_size).map_err(|e| format!("Failed to create slice quantity: {}", e)).unwrap(),
price: parent_order.price.clone(),
time_in_force: TimeInForce::IOC,
timestamp: parent_order.timestamp,
status: OrderStatus::New,
client_id: parent_order.client_id.clone(),
};
child_orders.push(child_order);
execution_schedule.push(start_time + chrono::Duration::from_std(slice_interval * i as u32).unwrap());
}
Ok(ExecutionPlan {
child_orders,
execution_schedule,
})
}
}
#[derive(Debug)]
struct IcebergExecutor {
clip_size: Quantity,
price_improvement_threshold: Decimal,
}
impl IcebergExecutor {
fn new(clip_size: Quantity, price_improvement_threshold: Decimal) -> Self {
Self { clip_size, price_improvement_threshold }
}
async fn get_initial_slice(&self, parent_order: &Order) -> Result<Order, Box<dyn std::error::Error>> {
let slice_quantity = std::cmp::min(parent_order.quantity.value(), self.clip_size.value());
Ok(Order {
id: OrderId::new(format!("{}-ICE-0", parent_order.id.value())),
symbol: parent_order.symbol.clone(),
side: parent_order.side,
order_type: parent_order.order_type,
quantity: Quantity::new(slice_quantity).map_err(|e| format!("Failed to create slice quantity: {}", e)).unwrap(),
price: parent_order.price.clone(),
time_in_force: TimeInForce::Day,
timestamp: parent_order.timestamp,
status: OrderStatus::New,
client_id: parent_order.client_id.clone(),
})
}
async fn handle_fill(&self, parent_order: &Order, _fill: &Fill) -> Result<Option<Order>, Box<dyn std::error::Error>> {
// Replenish the visible quantity
Ok(Some(Order {
id: OrderId::new(format!("{}-ICE-REFILL", parent_order.id.value())),
symbol: parent_order.symbol.clone(),
side: parent_order.side,
order_type: parent_order.order_type,
quantity: self.clip_size.clone(),
price: parent_order.price.clone(),
time_in_force: TimeInForce::Day,
timestamp: chrono::Utc::now(),
status: OrderStatus::New,
client_id: parent_order.client_id.clone(),
}))
}
}
#[derive(Debug)]
struct ExecutionResult {
order_id: OrderId,
symbol: Symbol,
expected_price: Price,
executed_price: Price,
quantity: Quantity,
execution_time: Duration,
venue: String,
timestamp: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug)]
struct ExecutionQualityMetrics {
average_slippage: Decimal,
total_executions: usize,
average_execution_time: Duration,
fill_rate: Decimal,
venue_performance: HashMap<String, VenueMetrics>,
}
#[derive(Debug)]
struct VenueMetrics {
average_slippage: Decimal,
execution_count: usize,
average_latency: Duration,
}
#[derive(Debug)]
struct ExecutionQualityTracker {
executions: Vec<ExecutionResult>,
}
impl ExecutionQualityTracker {
fn new() -> Self {
Self {
executions: Vec::new(),
}
}
fn record_execution(&mut self, execution: ExecutionResult) {
self.executions.push(execution);
}
async fn calculate_metrics(&self, period: Duration) -> Result<ExecutionQualityMetrics, Box<dyn std::error::Error>> {
let cutoff_time = chrono::Utc::now() - chrono::Duration::from_std(period).unwrap();
let recent_executions: Vec<_> = self.executions.iter()
.filter(|e| e.timestamp > cutoff_time)
.collect();
if recent_executions.is_empty() {
return Err("No executions in specified period".into());
}
// Calculate average slippage
let total_slippage: Decimal = recent_executions.iter()
.map(|e| e.executed_price.value() - e.expected_price.value())
.sum();
let average_slippage = total_slippage / Decimal::from(recent_executions.len());
// Calculate average execution time
let total_time: Duration = recent_executions.iter()
.map(|e| e.execution_time)
.sum();
let average_execution_time = total_time / recent_executions.len() as u32;
// Build venue performance map
let mut venue_performance = HashMap::new();
let mut venue_groups: HashMap<String, Vec<&ExecutionResult>> = HashMap::new();
for exec in &recent_executions {
venue_groups.entry(exec.venue.clone()).or_default().push(exec);
}
for (venue, execs) in venue_groups {
let venue_slippage: Decimal = execs.iter()
.map(|e| e.executed_price.value() - e.expected_price.value())
.sum::<Decimal>() / Decimal::from(execs.len());
let venue_latency = execs.iter()
.map(|e| e.execution_time)
.sum::<Duration>() / execs.len() as u32;
venue_performance.insert(venue, VenueMetrics {
average_slippage: venue_slippage,
execution_count: execs.len(),
average_latency: venue_latency,
});
}
Ok(ExecutionQualityMetrics {
average_slippage,
total_executions: recent_executions.len(),
average_execution_time,
fill_rate: Decimal::ONE, // Simplified - assume all filled
venue_performance,
})
}
}
#[derive(Debug)]
struct MultiVenueExecutor {
venue_allocations: HashMap<String, f64>,
}
#[derive(Debug)]
struct VenueOrder {
venue_id: String,
order: Order,
quantity: Quantity,
}
impl MultiVenueExecutor {
fn new() -> Self {
Self {
venue_allocations: HashMap::new(),
}
}
fn add_venue(&mut self, venue_id: &str, allocation: f64) {
self.venue_allocations.insert(venue_id.to_string(), allocation);
}
async fn split_order(&self, parent_order: &Order) -> Result<Vec<VenueOrder>, Box<dyn std::error::Error>> {
let mut venue_orders = Vec::new();
for (venue_id, &allocation) in &self.venue_allocations {
let venue_quantity = parent_order.quantity.value() * Decimal::from_f64(allocation).map_err(|e| format!("Failed to convert allocation to Decimal: {}", e)).unwrap();
if venue_quantity > Decimal::ZERO {
let venue_order = Order {
id: OrderId::new(format!("{}-{}", parent_order.id.value(), venue_id)),
symbol: parent_order.symbol.clone(),
side: parent_order.side,
order_type: parent_order.order_type,
quantity: Quantity::new(venue_quantity).map_err(|e| format!("Failed to create venue quantity: {}", e)).unwrap(),
price: parent_order.price.clone(),
time_in_force: parent_order.time_in_force,
timestamp: parent_order.timestamp,
status: OrderStatus::New,
client_id: parent_order.client_id.clone(),
};
venue_orders.push(VenueOrder {
venue_id: venue_id.clone(),
order: venue_order,
quantity: Quantity::new(venue_quantity).map_err(|e| format!("Failed to create venue quantity: {}", e)).unwrap(),
});
}
}
Ok(venue_orders)
}
}
#[derive(Debug)]
struct LatencyMetrics {
order_to_ack: Duration,
order_to_fill: Duration,
ack_to_fill: Duration,
}
#[derive(Debug)]
struct LatencyTracker {
timing_points: HashMap<OrderId, TimingPoints>,
}
#[derive(Debug)]
struct TimingPoints {
order_sent: Instant,
order_ack: Option<Instant>,
order_fill: Option<Instant>,
}
impl LatencyTracker {
fn new() -> Self {
Self {
timing_points: HashMap::new(),
}
}
fn mark_order_sent(&mut self, order_id: &OrderId, time: Instant) {
self.timing_points.insert(order_id.clone(), TimingPoints {
order_sent: time,
order_ack: None,
order_fill: None,
});
}
fn mark_order_ack(&mut self, order_id: &OrderId, time: Instant) {
if let Some(points) = self.timing_points.get_mut(order_id) {
points.order_ack = Some(time);
}
}
fn mark_order_fill(&mut self, order_id: &OrderId, time: Instant) {
if let Some(points) = self.timing_points.get_mut(order_id) {
points.order_fill = Some(time);
}
}
async fn get_metrics(&self, order_id: &OrderId) -> Result<LatencyMetrics, Box<dyn std::error::Error>> {
let points = self.timing_points.get(order_id)
.ok_or("Order not found in timing points")?;
let order_to_ack = points.order_ack
.ok_or("Order acknowledgment not recorded")?
.duration_since(points.order_sent);
let order_to_fill = points.order_fill
.ok_or("Order fill not recorded")?
.duration_since(points.order_sent);
let ack_to_fill = points.order_fill.unwrap()
.duration_since(points.order_ack.unwrap());
Ok(LatencyMetrics {
order_to_ack,
order_to_fill,
ack_to_fill,
})
}
}