Files
foxhunt/trading_engine/tests/compliance_best_execution.rs

510 lines
18 KiB
Rust

//! Best Execution Compliance Tests
//!
//! Comprehensive tests for MiFID II Best Execution requirements including:
//! - Execution quality metrics calculation
//! - Venue selection and analysis
//! - Transaction cost breakdown
//! - Best execution policy compliance
//! - RTS 28 reporting requirements
use trading_engine::compliance::best_execution::{
BestExecutionAnalyzer, BestExecutionConfig, VenueType, ExecutionFactors,
VenueSelectionCriteria, ReportingIntervals,
};
use trading_engine::compliance::{OrderInfo, MiFIDConfig};
use common::{OrderId, OrderSide, OrderType, Price, Quantity};
use rust_decimal::Decimal;
use chrono::Utc;
/// Test best execution analyzer initialization
#[tokio::test]
async fn test_best_execution_analyzer_initialization() {
let config = MiFIDConfig {
best_execution_enabled: true,
transaction_reporting_endpoint: Some("https://test.endpoint.com".to_string()),
client_categorization_enabled: true,
product_governance_enabled: true,
position_limit_monitoring: true,
};
let analyzer = BestExecutionAnalyzer::new(&config);
// Verify analyzer is properly initialized (no panic)
assert!(true, "Analyzer initialized successfully");
}
/// Test execution quality metrics calculation
#[tokio::test]
async fn test_execution_quality_metrics() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Buy,
order_type: OrderType::Limit,
quantity: Quantity::from_shares(1000),
price: Some(Price::from_f64(100.50).expect("Valid price")),
symbol: "AAPL".to_string(),
client_id: "CLIENT001".to_string(),
timestamp: Utc::now(),
};
let result = analyzer.analyze_best_execution(&order_info).await;
assert!(result.is_ok(), "Best execution analysis should succeed");
let analysis = result.unwrap();
assert_eq!(analysis.order_id, order_info.order_id);
assert!(analysis.execution_score >= 0.0 && analysis.execution_score <= 1.0,
"Execution score should be between 0 and 1");
// Verify quality metrics are populated
let metrics = &analysis.quality_metrics;
assert!(metrics.fill_rate >= 0.0 && metrics.fill_rate <= 1.0,
"Fill rate should be a valid percentage");
assert!(metrics.avg_execution_time_ms > 0,
"Execution time should be positive");
}
/// Test venue selection and scoring
#[tokio::test]
async fn test_venue_selection() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Buy,
order_type: OrderType::Market,
quantity: Quantity::from_shares(5000),
price: None, // Market order
symbol: "MSFT".to_string(),
client_id: "CLIENT002".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
// Verify venue was selected
assert!(!analysis.execution_venue.is_empty(), "Venue should be selected");
// Verify alternative venues were evaluated
assert!(!analysis.alternative_venues.is_empty(),
"Alternative venues should be evaluated");
// Verify venue scores are valid
for venue in &analysis.alternative_venues {
assert!(venue.venue_score >= 0.0 && venue.venue_score <= 1.0,
"Venue score should be between 0 and 1");
assert!(venue.execution_probability >= 0.0 && venue.execution_probability <= 1.0,
"Execution probability should be valid percentage");
}
}
/// Test transaction cost breakdown
#[tokio::test]
async fn test_transaction_cost_breakdown() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Sell,
order_type: OrderType::Limit,
quantity: Quantity::from_shares(2000),
price: Some(Price::from_f64(50.25).expect("Valid price")),
symbol: "GOOGL".to_string(),
client_id: "CLIENT003".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
let costs = &analysis.cost_analysis;
// Verify explicit costs are calculated
assert!(costs.explicit_costs.commission >= Decimal::ZERO,
"Commission should be non-negative");
assert!(costs.explicit_costs.exchange_fees >= Decimal::ZERO,
"Exchange fees should be non-negative");
assert!(costs.explicit_costs.clearing_fees >= Decimal::ZERO,
"Clearing fees should be non-negative");
// Verify implicit costs are calculated
assert!(costs.implicit_costs.spread_cost_bps >= 0.0,
"Spread cost should be non-negative");
assert!(costs.implicit_costs.market_impact_bps >= 0.0,
"Market impact should be non-negative");
// Verify total costs
assert!(costs.total_costs_bps > 0.0,
"Total costs should be positive");
}
/// Test best execution compliance assessment
#[tokio::test]
async fn test_best_execution_compliance() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Buy,
order_type: OrderType::Limit,
quantity: Quantity::from_shares(500),
price: Some(Price::from_f64(150.00).expect("Valid price")),
symbol: "TSLA".to_string(),
client_id: "CLIENT004".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
// For well-configured system, should be compliant
assert!(analysis.is_compliant || !analysis.findings.is_empty(),
"Should either be compliant or have findings explaining non-compliance");
// Verify execution score exists and is used for compliance
assert_eq!(analysis.execution_score, analysis.execution_quality_score,
"Execution score and quality score should match");
}
/// Test price improvement detection
#[tokio::test]
async fn test_price_improvement() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Buy,
order_type: OrderType::Limit,
quantity: Quantity::from_shares(1000),
price: Some(Price::from_f64(100.00).expect("Valid price")),
symbol: "NVDA".to_string(),
client_id: "CLIENT005".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
let metrics = &analysis.quality_metrics;
// Price improvement can be positive (better than NBBO) or negative (worse)
assert!(metrics.price_improvement_bps.is_finite(),
"Price improvement should be a valid number");
// Verify spread metrics
assert!(metrics.effective_spread_bps >= 0.0,
"Effective spread should be non-negative");
assert!(metrics.realized_spread_bps >= 0.0,
"Realized spread should be non-negative");
}
/// Test market impact calculation
#[tokio::test]
async fn test_market_impact() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
// Large order to test market impact
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Buy,
order_type: OrderType::Market,
quantity: Quantity::from_shares(100000),
price: None,
symbol: "AAPL".to_string(),
client_id: "CLIENT006".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
let metrics = &analysis.quality_metrics;
// Large orders should have measurable market impact
assert!(metrics.market_impact_bps >= 0.0,
"Market impact should be non-negative");
}
/// Test execution venue types
#[tokio::test]
async fn test_venue_types() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Buy,
order_type: OrderType::Limit,
quantity: Quantity::from_shares(1000),
price: Some(Price::from_f64(50.00).expect("Valid price")),
symbol: "AMZN".to_string(),
client_id: "CLIENT007".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
// Verify venue types are properly classified
for venue in &analysis.alternative_venues {
match &venue.venue_type {
VenueType::ReguLatedMarket |
VenueType::MTF |
VenueType::OTF |
VenueType::SystematicInternaliser |
VenueType::MarketMaker |
VenueType::OtherLiquidityProvider => {
// Valid venue type
assert!(true);
}
}
}
}
/// Test custom execution factors configuration
#[tokio::test]
async fn test_custom_execution_factors() {
let custom_config = BestExecutionConfig {
real_time_monitoring: true,
execution_factors: ExecutionFactors {
price_weight: 0.40,
cost_weight: 0.30,
speed_weight: 0.15,
likelihood_weight: 0.10,
size_weight: 0.03,
market_impact_weight: 0.02,
},
venue_criteria: VenueSelectionCriteria {
min_volume_threshold: Decimal::from(5000),
max_latency_tolerance: 500, // 500μs
min_execution_probability: 0.90,
max_price_deviation_bps: 10.0,
},
reporting_intervals: ReportingIntervals {
real_time_interval: 1,
daily_reports: true,
monthly_rts28_reports: true,
annual_summary: true,
},
min_analysis_period_days: 30,
};
// Verify config is valid
let total_weight = custom_config.execution_factors.price_weight
+ custom_config.execution_factors.cost_weight
+ custom_config.execution_factors.speed_weight
+ custom_config.execution_factors.likelihood_weight
+ custom_config.execution_factors.size_weight
+ custom_config.execution_factors.market_impact_weight;
assert!((total_weight - 1.0).abs() < 0.01,
"Execution factor weights should sum to ~1.0");
}
/// Test execution findings generation
#[tokio::test]
async fn test_execution_findings() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Buy,
order_type: OrderType::Limit,
quantity: Quantity::from_shares(1000),
price: Some(Price::from_f64(100.00).expect("Valid price")),
symbol: "META".to_string(),
client_id: "CLIENT008".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
// Findings should be empty for compliant execution or contain valid issues
for finding in &analysis.findings {
assert!(!finding.description.is_empty(),
"Finding description should not be empty");
assert!(!finding.remedial_action.is_empty(),
"Remedial action should be specified");
}
}
/// Test execution documentation
#[tokio::test]
async fn test_execution_documentation() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Sell,
order_type: OrderType::Limit,
quantity: Quantity::from_shares(2000),
price: Some(Price::from_f64(75.50).expect("Valid price")),
symbol: "NFLX".to_string(),
client_id: "CLIENT009".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
let docs = &analysis.documentation;
// Verify documentation is complete
assert!(!docs.venue_evaluation.is_empty(),
"Venue evaluation should be documented");
assert!(!docs.cost_benefit_analysis.is_empty(),
"Cost-benefit analysis should be documented");
assert!(!docs.decision_rationale.is_empty(),
"Decision rationale should be documented");
// Verify market conditions snapshot
assert!(docs.market_conditions.volatility >= 0.0,
"Volatility should be non-negative");
assert!(docs.market_conditions.liquidity_depth >= Decimal::ZERO,
"Liquidity depth should be non-negative");
}
/// Test high-frequency trading execution quality
#[tokio::test]
async fn test_hft_execution_quality() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
// Small HFT order
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Buy,
order_type: OrderType::Market,
quantity: Quantity::from_shares(100),
price: None,
symbol: "SPY".to_string(),
client_id: "HFT_CLIENT".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
let metrics = &analysis.quality_metrics;
// HFT orders should have low execution times
assert!(metrics.avg_execution_time_ms < 1000,
"HFT execution should be fast (< 1 second)");
// High fill rate expected for liquid instruments
assert!(metrics.fill_rate > 0.90,
"Fill rate should be high for liquid instruments");
}
/// Test multi-venue execution analysis
#[tokio::test]
async fn test_multi_venue_analysis() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Buy,
order_type: OrderType::Limit,
quantity: Quantity::from_shares(10000),
price: Some(Price::from_f64(200.00).expect("Valid price")),
symbol: "GOOG".to_string(),
client_id: "CLIENT010".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
// Should evaluate multiple venues
assert!(analysis.alternative_venues.len() >= 1,
"Should evaluate at least one alternative venue");
// Verify venues have different characteristics
let mut venue_ids: Vec<String> = analysis.alternative_venues
.iter()
.map(|v| v.venue_id.clone())
.collect();
venue_ids.sort();
venue_ids.dedup();
assert_eq!(venue_ids.len(), analysis.alternative_venues.len(),
"Venue IDs should be unique");
}
/// Test cost methodology validation
#[tokio::test]
async fn test_cost_methodology() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Buy,
order_type: OrderType::Limit,
quantity: Quantity::from_shares(1000),
price: Some(Price::from_f64(100.00).expect("Valid price")),
symbol: "AMD".to_string(),
client_id: "CLIENT011".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
// Verify cost methodology is documented
assert!(!analysis.cost_analysis.methodology.is_empty(),
"Cost methodology should be documented");
assert!(analysis.cost_analysis.methodology.contains("MiFID II") ||
analysis.cost_analysis.methodology.contains("RTS 28"),
"Should reference regulatory requirements");
}
/// Test execution score calculation accuracy
#[tokio::test]
async fn test_execution_score_accuracy() {
let config = MiFIDConfig::default();
let analyzer = BestExecutionAnalyzer::new(&config);
let order_info = OrderInfo {
order_id: OrderId::new(),
side: OrderSide::Buy,
order_type: OrderType::Limit,
quantity: Quantity::from_shares(1000),
price: Some(Price::from_f64(100.00).expect("Valid price")),
symbol: "INTC".to_string(),
client_id: "CLIENT012".to_string(),
timestamp: Utc::now(),
};
let analysis = analyzer.analyze_best_execution(&order_info).await
.expect("Analysis should succeed");
// Execution score should reflect quality metrics and costs
let score = analysis.execution_score;
let metrics = &analysis.quality_metrics;
let costs = &analysis.cost_analysis;
// High fill rate and low costs should correlate with higher score
if metrics.fill_rate > 0.95 && costs.total_costs_bps < 10.0 {
assert!(score > 0.7,
"Good execution should have high score");
}
// Verify score is normalized
assert!(score >= 0.0 && score <= 1.0,
"Score should be between 0 and 1");
}