Wave 64-65 cleanup: Proto regeneration and build system updates from Tonic 0.12→0.14 upgrade Files updated: - Cargo.lock: Dependency resolution for Tonic 0.14.2 - All build.rs: Updated for tonic-prost-build - Proto files: Regenerated with tonic-prost 0.14 - Examples/tests: Updated for new gRPC API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1291 lines
45 KiB
Rust
1291 lines
45 KiB
Rust
//! Trading ↔ Risk Management Integration Tests
|
|
//!
|
|
//! This module provides comprehensive integration testing between the Trading system
|
|
//! and Risk Management components. Tests cover:
|
|
//!
|
|
//! ## Test Coverage Areas
|
|
//! - Pre-trade risk checks (position limits, VaR)
|
|
//! - Kelly sizing calculations for position sizing
|
|
//! - Real-time risk monitoring during trades
|
|
//! - Kill switch activation under stress conditions
|
|
//! - Portfolio rebalancing triggers
|
|
//! - VaR calculation accuracy across market regimes
|
|
//! - Risk limit enforcement and escalation
|
|
//! - Performance validation under HFT requirements
|
|
//!
|
|
//! ## Architecture Under Test
|
|
//! ```
|
|
//! Trading Engine ←→ Risk Management ←→ Portfolio Monitor
|
|
//! ↓ ↓ ↓
|
|
//! Order Validation Position Limits Risk Metrics
|
|
//! ↓ ↓ ↓
|
|
//! Execution Logic VaR Models Kill Switches
|
|
//! ```
|
|
#![allow(unused_crate_dependencies)]
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
use tokio::sync::{RwLock, mpsc, Mutex};
|
|
use tokio::time::timeout;
|
|
use uuid::Uuid;
|
|
|
|
// Import core system types
|
|
use trading_engine::timing::HardwareTimestamp;
|
|
// use risk::prelude::*; // REMOVED - prelude does not exist
|
|
|
|
/// Test result type for safe error handling
|
|
type TestResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
|
|
|
/// Trading-Risk integration test configuration
|
|
#[derive(Debug, Clone)]
|
|
pub struct TradingRiskIntegrationConfig {
|
|
/// Maximum risk check latency for HFT (microseconds)
|
|
pub max_risk_check_latency_us: u64,
|
|
/// Maximum position limit check latency
|
|
pub max_position_check_latency_us: u64,
|
|
/// Maximum VaR calculation latency
|
|
pub max_var_calculation_latency_ms: u64,
|
|
/// Test portfolio initial value
|
|
pub test_portfolio_value: Decimal,
|
|
/// Maximum position size as percentage of portfolio
|
|
pub max_position_size_pct: f64,
|
|
/// VaR confidence level
|
|
pub var_confidence_level: f64,
|
|
/// Kill switch activation threshold (percentage loss)
|
|
pub kill_switch_threshold_pct: f64,
|
|
/// Test symbols for risk validation
|
|
pub test_symbols: Vec<String>,
|
|
/// Stress test scenarios
|
|
pub stress_test_scenarios: Vec<StressTestScenario>,
|
|
}
|
|
|
|
impl Default for TradingRiskIntegrationConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
max_risk_check_latency_us: 5_000, // 5ms for HFT
|
|
max_position_check_latency_us: 1_000, // 1ms position check
|
|
max_var_calculation_latency_ms: 100, // 100ms VaR calc
|
|
test_portfolio_value: Decimal::new(1_000_000_00, 2), // $1M portfolio
|
|
max_position_size_pct: 0.05, // 5% max position
|
|
var_confidence_level: 0.95, // 95% VaR confidence
|
|
kill_switch_threshold_pct: 0.02, // 2% loss threshold
|
|
test_symbols: vec!["EURUSD".to_string(), "GBPUSD".to_string(), "USDJPY".to_string()],
|
|
stress_test_scenarios: vec![
|
|
StressTestScenario::MarketCrash { severity: 0.1 },
|
|
StressTestScenario::VolatilitySpike { multiplier: 3.0 },
|
|
StressTestScenario::LiquidityDrain { reduction: 0.8 },
|
|
],
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum StressTestScenario {
|
|
MarketCrash { severity: f64 },
|
|
VolatilitySpike { multiplier: f64 },
|
|
LiquidityDrain { reduction: f64 },
|
|
}
|
|
|
|
/// Trading-Risk integration test suite
|
|
pub struct TradingRiskIntegrationSuite {
|
|
config: TradingRiskIntegrationConfig,
|
|
risk_manager: Arc<RiskManager>,
|
|
portfolio_monitor: Arc<PortfolioMonitor>,
|
|
var_calculator: Arc<VarCalculator>,
|
|
kelly_optimizer: Arc<KellyOptimizer>,
|
|
position_limiter: Arc<PositionLimiter>,
|
|
kill_switch: Arc<KillSwitch>,
|
|
performance_tracker: Arc<RiskPerformanceTracker>,
|
|
test_portfolio: Arc<RwLock<TestPortfolio>>,
|
|
}
|
|
|
|
impl TradingRiskIntegrationSuite {
|
|
/// Create new Trading-Risk integration test suite
|
|
pub async fn new(config: TradingRiskIntegrationConfig) -> TestResult<Self> {
|
|
// Initialize risk management components
|
|
let risk_manager = Arc::new(
|
|
RiskManager::new(RiskConfig {
|
|
max_portfolio_risk: config.max_position_size_pct,
|
|
var_confidence: config.var_confidence_level,
|
|
kill_switch_threshold: config.kill_switch_threshold_pct,
|
|
max_position_concentration: 0.1, // 10% max single position
|
|
..Default::default()
|
|
}).await
|
|
.map_err(|e| format!("Failed to create risk manager: {}", e))?
|
|
);
|
|
|
|
let portfolio_monitor = Arc::new(
|
|
PortfolioMonitor::new(config.test_portfolio_value).await
|
|
.map_err(|e| format!("Failed to create portfolio monitor: {}", e))?
|
|
);
|
|
|
|
let var_calculator = Arc::new(
|
|
VarCalculator::new(config.var_confidence_level).await
|
|
.map_err(|e| format!("Failed to create VaR calculator: {}", e))?
|
|
);
|
|
|
|
let kelly_optimizer = Arc::new(
|
|
KellyOptimizer::new().await
|
|
.map_err(|e| format!("Failed to create Kelly optimizer: {}", e))?
|
|
);
|
|
|
|
let position_limiter = Arc::new(
|
|
PositionLimiter::new(config.max_position_size_pct).await
|
|
.map_err(|e| format!("Failed to create position limiter: {}", e))?
|
|
);
|
|
|
|
let kill_switch = Arc::new(
|
|
KillSwitch::new(config.kill_switch_threshold_pct).await
|
|
.map_err(|e| format!("Failed to create kill switch: {}", e))?
|
|
);
|
|
|
|
let performance_tracker = Arc::new(RiskPerformanceTracker::new());
|
|
|
|
// Initialize test portfolio
|
|
let test_portfolio = Arc::new(RwLock::new(
|
|
TestPortfolio::new(config.test_portfolio_value)
|
|
));
|
|
|
|
Ok(Self {
|
|
config,
|
|
risk_manager,
|
|
portfolio_monitor,
|
|
var_calculator,
|
|
kelly_optimizer,
|
|
position_limiter,
|
|
kill_switch,
|
|
performance_tracker,
|
|
test_portfolio,
|
|
})
|
|
}
|
|
|
|
/// Test pre-trade risk checks with position limits and VaR
|
|
pub async fn test_pretrade_risk_checks(&self) -> TestResult<()> {
|
|
let mut risk_check_latencies = Vec::new();
|
|
let mut approval_rate = 0.0;
|
|
let mut total_orders = 0;
|
|
let mut approved_orders = 0;
|
|
|
|
for symbol in &self.config.test_symbols {
|
|
// Test various order sizes
|
|
let order_sizes = vec![
|
|
Decimal::new(10_000_00, 2), // $10K - should pass
|
|
Decimal::new(50_000_00, 2), // $50K - might pass
|
|
Decimal::new(100_000_00, 2), // $100K - risky
|
|
Decimal::new(500_000_00, 2), // $500K - should reject
|
|
];
|
|
|
|
for order_size in order_sizes {
|
|
let order = TestOrder {
|
|
id: format!("TEST_ORDER_{}", Uuid::new_v4()),
|
|
symbol: symbol.clone(),
|
|
side: OrderSide::Buy,
|
|
quantity: order_size / Decimal::new(110_00, 2), // Assume $1.10 price
|
|
price: Decimal::new(110_00, 2),
|
|
order_type: OrderType::Market,
|
|
timestamp: HardwareTimestamp::now(),
|
|
};
|
|
|
|
let start_time = HardwareTimestamp::now();
|
|
|
|
// Pre-trade risk check
|
|
let risk_result = self.risk_manager
|
|
.validate_order(&order)
|
|
.await
|
|
.map_err(|e| format!("Risk validation failed: {}", e))?;
|
|
|
|
let risk_latency = HardwareTimestamp::now().latency_ns(&start_time);
|
|
risk_check_latencies.push(risk_latency);
|
|
|
|
// Validate risk check latency
|
|
assert!(
|
|
risk_latency < self.config.max_risk_check_latency_us * 1_000,
|
|
"Risk check latency {}μs exceeds requirement {}μs for order {}",
|
|
risk_latency / 1_000,
|
|
self.config.max_risk_check_latency_us,
|
|
order.id
|
|
);
|
|
|
|
total_orders += 1;
|
|
if risk_result.approved {
|
|
approved_orders += 1;
|
|
}
|
|
|
|
// Validate risk assessment structure
|
|
assert!(
|
|
risk_result.risk_score >= 0.0 && risk_result.risk_score <= 1.0,
|
|
"Risk score should be between 0 and 1"
|
|
);
|
|
|
|
// Large orders should be rejected
|
|
if order_size > Decimal::new(200_000_00, 2) {
|
|
assert!(
|
|
!risk_result.approved,
|
|
"Large order of ${} should be rejected",
|
|
order_size
|
|
);
|
|
}
|
|
|
|
self.performance_tracker
|
|
.record_risk_check_latency(risk_latency / 1_000)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
approval_rate = approved_orders as f64 / total_orders as f64;
|
|
let avg_risk_latency = risk_check_latencies.iter().sum::<u64>() / risk_check_latencies.len() as u64;
|
|
|
|
// Validate risk system behavior
|
|
assert!(
|
|
approval_rate >= 0.3 && approval_rate <= 0.8,
|
|
"Risk approval rate {:.1}% should be between 30% and 80%",
|
|
approval_rate * 100.0
|
|
);
|
|
|
|
assert!(
|
|
avg_risk_latency < self.config.max_risk_check_latency_us * 1_000,
|
|
"Average risk check latency {}μs exceeds requirement {}μs",
|
|
avg_risk_latency / 1_000,
|
|
self.config.max_risk_check_latency_us
|
|
);
|
|
|
|
println!("✓ Pre-trade risk checks test passed:");
|
|
println!(" Orders tested: {}", total_orders);
|
|
println!(" Approval rate: {:.1}%", approval_rate * 100.0);
|
|
println!(" Average risk latency: {}μs", avg_risk_latency / 1_000);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Test Kelly sizing calculations for optimal position sizing
|
|
pub async fn test_kelly_sizing_optimization(&self) -> TestResult<()> {
|
|
let mut kelly_calculations = Vec::new();
|
|
|
|
for symbol in &self.config.test_symbols {
|
|
// Simulate different market conditions for Kelly sizing
|
|
let market_scenarios = vec![
|
|
MarketCondition { win_rate: 0.6, avg_win: 0.02, avg_loss: -0.01 }, // Favorable
|
|
MarketCondition { win_rate: 0.5, avg_win: 0.015, avg_loss: -0.015 }, // Neutral
|
|
MarketCondition { win_rate: 0.4, avg_win: 0.03, avg_loss: -0.02 }, // High risk/reward
|
|
];
|
|
|
|
for condition in market_scenarios {
|
|
let start_time = HardwareTimestamp::now();
|
|
|
|
// Calculate Kelly optimal position size
|
|
let kelly_result = self.kelly_optimizer
|
|
.calculate_optimal_size(
|
|
symbol,
|
|
condition.win_rate,
|
|
condition.avg_win,
|
|
condition.avg_loss.abs(),
|
|
self.config.test_portfolio_value,
|
|
)
|
|
.await
|
|
.map_err(|e| format!("Kelly calculation failed: {}", e))?;
|
|
|
|
let kelly_latency = HardwareTimestamp::now().latency_ns(&start_time);
|
|
|
|
// Validate Kelly calculation latency
|
|
assert!(
|
|
kelly_latency < 50_000_000, // 50ms max
|
|
"Kelly calculation latency {}ms exceeds 50ms limit",
|
|
kelly_latency / 1_000_000
|
|
);
|
|
|
|
// Validate Kelly sizing logic
|
|
assert!(
|
|
kelly_result.optimal_fraction >= 0.0 && kelly_result.optimal_fraction <= 1.0,
|
|
"Kelly fraction should be between 0 and 1"
|
|
);
|
|
|
|
// Favorable conditions should suggest larger positions
|
|
if condition.win_rate > 0.55 && condition.avg_win > condition.avg_loss.abs() {
|
|
assert!(
|
|
kelly_result.optimal_fraction > 0.1,
|
|
"Favorable conditions should suggest position > 10%"
|
|
);
|
|
}
|
|
|
|
// Poor conditions should suggest smaller positions
|
|
if condition.win_rate < 0.45 {
|
|
assert!(
|
|
kelly_result.optimal_fraction < 0.1,
|
|
"Poor conditions should suggest position < 10%"
|
|
);
|
|
}
|
|
|
|
kelly_calculations.push(kelly_result);
|
|
|
|
self.performance_tracker
|
|
.record_kelly_calculation(kelly_latency / 1_000)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
let avg_kelly_fraction = kelly_calculations.iter()
|
|
.map(|k| k.optimal_fraction)
|
|
.sum::<f64>() / kelly_calculations.len() as f64;
|
|
|
|
assert!(
|
|
avg_kelly_fraction > 0.0 && avg_kelly_fraction < 0.5,
|
|
"Average Kelly fraction {:.3} should be reasonable",
|
|
avg_kelly_fraction
|
|
);
|
|
|
|
println!("✓ Kelly sizing optimization test passed:");
|
|
println!(" Calculations performed: {}", kelly_calculations.len());
|
|
println!(" Average Kelly fraction: {:.3}", avg_kelly_fraction);
|
|
println!(" Optimal sizing under various market conditions");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Test real-time risk monitoring during active trades
|
|
pub async fn test_realtime_risk_monitoring(&self) -> TestResult<()> {
|
|
// Setup initial portfolio positions
|
|
let mut portfolio = self.test_portfolio.write().await;
|
|
|
|
// Add test positions
|
|
for symbol in &self.config.test_symbols {
|
|
portfolio.add_position(Position {
|
|
symbol: symbol.clone(),
|
|
quantity: Decimal::new(10_000, 0),
|
|
average_price: Decimal::new(110_00, 2),
|
|
current_price: Decimal::new(110_00, 2),
|
|
unrealized_pnl: Decimal::ZERO,
|
|
market_value: Decimal::new(1_100_000_00, 2),
|
|
});
|
|
}
|
|
drop(portfolio);
|
|
|
|
let mut monitoring_cycles = 0;
|
|
let mut risk_violations = 0;
|
|
|
|
// Simulate 100 monitoring cycles
|
|
for cycle in 0..100 {
|
|
let start_time = HardwareTimestamp::now();
|
|
|
|
// Simulate price changes
|
|
self.simulate_price_changes().await?;
|
|
|
|
// Update portfolio with new prices
|
|
self.update_portfolio_prices().await?;
|
|
|
|
// Run risk monitoring cycle
|
|
let risk_status = self.portfolio_monitor
|
|
.assess_portfolio_risk()
|
|
.await
|
|
.map_err(|e| format!("Portfolio risk assessment failed: {}", e))?;
|
|
|
|
let monitoring_latency = HardwareTimestamp::now().latency_ns(&start_time);
|
|
|
|
// Validate monitoring latency
|
|
assert!(
|
|
monitoring_latency < 10_000_000, // 10ms max
|
|
"Risk monitoring latency {}ms exceeds 10ms limit",
|
|
monitoring_latency / 1_000_000
|
|
);
|
|
|
|
// Check risk metrics
|
|
assert!(
|
|
risk_status.total_var >= Decimal::ZERO,
|
|
"VaR should be non-negative"
|
|
);
|
|
|
|
assert!(
|
|
risk_status.portfolio_beta.is_finite(),
|
|
"Portfolio beta should be finite"
|
|
);
|
|
|
|
if risk_status.risk_level > RiskLevel::Medium {
|
|
risk_violations += 1;
|
|
}
|
|
|
|
monitoring_cycles += 1;
|
|
|
|
self.performance_tracker
|
|
.record_monitoring_cycle(monitoring_latency / 1_000)
|
|
.await;
|
|
|
|
// Small delay to simulate real-time monitoring
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
}
|
|
|
|
let violation_rate = risk_violations as f64 / monitoring_cycles as f64;
|
|
|
|
// Validate monitoring system
|
|
assert!(
|
|
violation_rate < 0.3, // Less than 30% violations expected
|
|
"Risk violation rate {:.1}% should be < 30%",
|
|
violation_rate * 100.0
|
|
);
|
|
|
|
println!("✓ Real-time risk monitoring test passed:");
|
|
println!(" Monitoring cycles: {}", monitoring_cycles);
|
|
println!(" Risk violations: {} ({:.1}%)", risk_violations, violation_rate * 100.0);
|
|
println!(" Continuous portfolio risk assessment");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Test kill switch activation under stress conditions
|
|
pub async fn test_kill_switch_activation(&self) -> TestResult<()> {
|
|
let mut kill_switch_activations = 0;
|
|
|
|
for scenario in &self.config.stress_test_scenarios {
|
|
println!("Testing kill switch under scenario: {:?}", scenario);
|
|
|
|
// Reset kill switch state
|
|
self.kill_switch.reset().await?;
|
|
|
|
// Apply stress scenario
|
|
self.apply_stress_scenario(scenario).await?;
|
|
|
|
// Monitor for kill switch activation
|
|
let mut monitoring_duration = 0;
|
|
let max_monitoring_duration = 30; // 30 cycles max
|
|
|
|
while monitoring_duration < max_monitoring_duration {
|
|
let portfolio_state = self.portfolio_monitor
|
|
.assess_portfolio_risk()
|
|
.await?;
|
|
|
|
// Check if kill switch should activate
|
|
let kill_switch_status = self.kill_switch
|
|
.evaluate_activation(&portfolio_state)
|
|
.await?;
|
|
|
|
if kill_switch_status.should_activate {
|
|
kill_switch_activations += 1;
|
|
|
|
// Validate kill switch response time
|
|
assert!(
|
|
kill_switch_status.response_time_ms < 100,
|
|
"Kill switch response time {}ms should be < 100ms",
|
|
kill_switch_status.response_time_ms
|
|
);
|
|
|
|
// Test emergency position liquidation
|
|
let liquidation_result = self.kill_switch
|
|
.execute_emergency_liquidation()
|
|
.await?;
|
|
|
|
assert!(
|
|
liquidation_result.positions_liquidated > 0,
|
|
"Kill switch should liquidate positions"
|
|
);
|
|
|
|
assert!(
|
|
liquidation_result.liquidation_time_ms < 5000, // 5 seconds
|
|
"Emergency liquidation should complete in < 5 seconds"
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
monitoring_duration += 1;
|
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
}
|
|
}
|
|
|
|
assert!(
|
|
kill_switch_activations > 0,
|
|
"Kill switch should activate during stress scenarios"
|
|
);
|
|
|
|
assert!(
|
|
kill_switch_activations <= self.config.stress_test_scenarios.len(),
|
|
"Kill switch should not activate multiple times per scenario"
|
|
);
|
|
|
|
println!("✓ Kill switch activation test passed:");
|
|
println!(" Stress scenarios tested: {}", self.config.stress_test_scenarios.len());
|
|
println!(" Kill switch activations: {}", kill_switch_activations);
|
|
println!(" Emergency procedures validated");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Test VaR calculation accuracy across different market regimes
|
|
pub async fn test_var_calculation_accuracy(&self) -> TestResult<()> {
|
|
let market_regimes = vec![
|
|
MarketRegime::TrendingUp { strength: 0.8 },
|
|
MarketRegime::TrendingDown { strength: 0.8 },
|
|
MarketRegime::Sideways { volatility: 0.1 },
|
|
MarketRegime::HighVolatility { volatility: 0.3 },
|
|
];
|
|
|
|
let mut var_calculations = Vec::new();
|
|
|
|
for regime in market_regimes {
|
|
// Generate market data for the regime
|
|
let market_data = self.generate_regime_market_data(®ime).await?;
|
|
|
|
let start_time = HardwareTimestamp::now();
|
|
|
|
// Calculate VaR for the regime
|
|
let var_result = self.var_calculator
|
|
.calculate_portfolio_var(&market_data, self.config.var_confidence_level)
|
|
.await
|
|
.map_err(|e| format!("VaR calculation failed: {}", e))?;
|
|
|
|
let var_latency = HardwareTimestamp::now().latency_ns(&start_time);
|
|
|
|
// Validate VaR calculation latency
|
|
assert!(
|
|
var_latency < self.config.max_var_calculation_latency_ms * 1_000_000,
|
|
"VaR calculation latency {}ms exceeds requirement {}ms",
|
|
var_latency / 1_000_000,
|
|
self.config.max_var_calculation_latency_ms
|
|
);
|
|
|
|
// Validate VaR values
|
|
assert!(
|
|
var_result.daily_var > Decimal::ZERO,
|
|
"Daily VaR should be positive"
|
|
);
|
|
|
|
assert!(
|
|
var_result.marginal_var.len() == self.config.test_symbols.len(),
|
|
"Marginal VaR should be calculated for all positions"
|
|
);
|
|
|
|
// High volatility regimes should produce higher VaR
|
|
match regime {
|
|
MarketRegime::HighVolatility { .. } => {
|
|
assert!(
|
|
var_result.daily_var > Decimal::new(10_000_00, 2), // $10K minimum
|
|
"High volatility should produce higher VaR"
|
|
);
|
|
}
|
|
MarketRegime::Sideways { .. } => {
|
|
assert!(
|
|
var_result.daily_var < Decimal::new(50_000_00, 2), // $50K maximum
|
|
"Low volatility should produce lower VaR"
|
|
);
|
|
}
|
|
_ => {} // Other regimes have intermediate expectations
|
|
}
|
|
|
|
var_calculations.push(var_result);
|
|
|
|
self.performance_tracker
|
|
.record_var_calculation(var_latency / 1_000)
|
|
.await;
|
|
}
|
|
|
|
// Validate VaR calculation consistency
|
|
let var_values: Vec<f64> = var_calculations.iter()
|
|
.map(|v| v.daily_var.to_f64())
|
|
.collect();
|
|
|
|
let var_range = var_values.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap() -
|
|
var_values.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
|
|
|
|
assert!(
|
|
var_range > 1000.0, // VaR should vary across regimes
|
|
"VaR should vary significantly across market regimes, range: ${:.2}",
|
|
var_range
|
|
);
|
|
|
|
println!("✓ VaR calculation accuracy test passed:");
|
|
println!(" Market regimes tested: {}", var_calculations.len());
|
|
println!(" VaR range across regimes: ${:.2}", var_range);
|
|
println!(" Accurate risk measurement across conditions");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Test portfolio rebalancing triggers and execution
|
|
pub async fn test_portfolio_rebalancing(&self) -> TestResult<()> {
|
|
// Setup imbalanced portfolio
|
|
let mut portfolio = self.test_portfolio.write().await;
|
|
portfolio.clear_positions();
|
|
|
|
// Create concentration in one position (should trigger rebalancing)
|
|
portfolio.add_position(Position {
|
|
symbol: "EURUSD".to_string(),
|
|
quantity: Decimal::new(80_000, 0),
|
|
average_price: Decimal::new(110_00, 2),
|
|
current_price: Decimal::new(115_00, 2), // 5% gain
|
|
unrealized_pnl: Decimal::new(40_000_00, 2),
|
|
market_value: Decimal::new(920_000_00, 2), // 92% of portfolio
|
|
});
|
|
|
|
// Small positions in other symbols
|
|
portfolio.add_position(Position {
|
|
symbol: "GBPUSD".to_string(),
|
|
quantity: Decimal::new(3_000, 0),
|
|
average_price: Decimal::new(130_00, 2),
|
|
current_price: Decimal::new(128_00, 2),
|
|
unrealized_pnl: Decimal::new(-6_000_00, 2),
|
|
market_value: Decimal::new(38_400_00, 2), // 3.8% of portfolio
|
|
});
|
|
drop(portfolio);
|
|
|
|
let start_time = HardwareTimestamp::now();
|
|
|
|
// Check if rebalancing is needed
|
|
let rebalance_analysis = self.portfolio_monitor
|
|
.analyze_rebalancing_needs()
|
|
.await?;
|
|
|
|
let rebalance_latency = HardwareTimestamp::now().latency_ns(&start_time);
|
|
|
|
// Validate rebalancing analysis
|
|
assert!(
|
|
rebalance_analysis.needs_rebalancing,
|
|
"Concentrated portfolio should need rebalancing"
|
|
);
|
|
|
|
assert!(
|
|
rebalance_analysis.concentration_risk > 0.8,
|
|
"Concentration risk should be high (>80%)"
|
|
);
|
|
|
|
assert!(
|
|
!rebalance_analysis.recommended_trades.is_empty(),
|
|
"Rebalancing should recommend trades"
|
|
);
|
|
|
|
// Execute rebalancing trades
|
|
let rebalance_execution = self.portfolio_monitor
|
|
.execute_rebalancing(&rebalance_analysis.recommended_trades)
|
|
.await?;
|
|
|
|
// Validate rebalancing execution
|
|
assert!(
|
|
rebalance_execution.trades_executed > 0,
|
|
"Rebalancing should execute trades"
|
|
);
|
|
|
|
assert!(
|
|
rebalance_execution.execution_time_ms < 5000, // 5 seconds
|
|
"Rebalancing should complete quickly"
|
|
);
|
|
|
|
// Verify portfolio is more balanced after rebalancing
|
|
let final_analysis = self.portfolio_monitor
|
|
.analyze_rebalancing_needs()
|
|
.await?;
|
|
|
|
assert!(
|
|
final_analysis.concentration_risk < 0.6,
|
|
"Concentration risk should be reduced after rebalancing"
|
|
);
|
|
|
|
println!("✓ Portfolio rebalancing test passed:");
|
|
println!(" Initial concentration risk: {:.1}%", rebalance_analysis.concentration_risk * 100.0);
|
|
println!(" Final concentration risk: {:.1}%", final_analysis.concentration_risk * 100.0);
|
|
println!(" Trades executed: {}", rebalance_execution.trades_executed);
|
|
println!(" Rebalancing latency: {}ms", rebalance_latency / 1_000_000);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper methods for test implementation
|
|
async fn simulate_price_changes(&self) -> TestResult<()> {
|
|
// Simulate realistic price movements
|
|
let price_changes = vec![
|
|
("EURUSD", 0.001), // +0.1 pip
|
|
("GBPUSD", -0.002), // -0.2 pip
|
|
("USDJPY", 0.0005), // +0.05 pip
|
|
];
|
|
|
|
for (symbol, change) in price_changes {
|
|
// This would update market data in a real system
|
|
// For testing, we just simulate the change
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_portfolio_prices(&self) -> TestResult<()> {
|
|
let mut portfolio = self.test_portfolio.write().await;
|
|
|
|
// Update prices based on simulated changes
|
|
for position in &mut portfolio.positions {
|
|
let price_change = (rand::random::<f64>() - 0.5) * 0.002; // ±0.1% random change
|
|
let new_price = position.current_price * (Decimal::ONE + Decimal::try_from(price_change).unwrap());
|
|
position.current_price = new_price;
|
|
position.market_value = position.quantity * new_price;
|
|
position.unrealized_pnl = position.market_value - (position.quantity * position.average_price);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn apply_stress_scenario(&self, scenario: &StressTestScenario) -> TestResult<()> {
|
|
let mut portfolio = self.test_portfolio.write().await;
|
|
|
|
match scenario {
|
|
StressTestScenario::MarketCrash { severity } => {
|
|
// Apply severe price drops
|
|
for position in &mut portfolio.positions {
|
|
position.current_price = position.current_price * (Decimal::ONE - Decimal::try_from(*severity).unwrap());
|
|
position.market_value = position.quantity * position.current_price;
|
|
position.unrealized_pnl = position.market_value - (position.quantity * position.average_price);
|
|
}
|
|
}
|
|
StressTestScenario::VolatilitySpike { multiplier } => {
|
|
// Increase price volatility
|
|
for position in &mut portfolio.positions {
|
|
let volatility = (rand::random::<f64>() - 0.5) * 0.1 * multiplier; // Enhanced volatility
|
|
position.current_price = position.current_price * (Decimal::ONE + Decimal::try_from(volatility).unwrap());
|
|
position.market_value = position.quantity * position.current_price;
|
|
position.unrealized_pnl = position.market_value - (position.quantity * position.average_price);
|
|
}
|
|
}
|
|
StressTestScenario::LiquidityDrain { reduction: _ } => {
|
|
// Simulate liquidity issues (for testing, just stress prices)
|
|
for position in &mut portfolio.positions {
|
|
position.current_price = position.current_price * Decimal::new(95, 2); // 5% haircut
|
|
position.market_value = position.quantity * position.current_price;
|
|
position.unrealized_pnl = position.market_value - (position.quantity * position.average_price);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn generate_regime_market_data(&self, regime: &MarketRegime) -> TestResult<Vec<MarketDataPoint>> {
|
|
let mut data_points = Vec::new();
|
|
let base_price = 1.1000;
|
|
|
|
for i in 0..252 { // One year of daily data
|
|
let price = match regime {
|
|
MarketRegime::TrendingUp { strength } => {
|
|
base_price + (i as f64 * 0.0001 * strength)
|
|
}
|
|
MarketRegime::TrendingDown { strength } => {
|
|
base_price - (i as f64 * 0.0001 * strength)
|
|
}
|
|
MarketRegime::Sideways { volatility } => {
|
|
base_price + (i as f64 / 50.0).sin() * volatility
|
|
}
|
|
MarketRegime::HighVolatility { volatility } => {
|
|
base_price + (rand::random::<f64>() - 0.5) * volatility * 2.0
|
|
}
|
|
};
|
|
|
|
data_points.push(MarketDataPoint {
|
|
symbol: "EURUSD".to_string(),
|
|
timestamp: HardwareTimestamp::now(),
|
|
price: Decimal::try_from(price).unwrap(),
|
|
volume: 1000000,
|
|
volatility: match regime {
|
|
MarketRegime::HighVolatility { volatility } => *volatility,
|
|
MarketRegime::Sideways { volatility } => *volatility,
|
|
_ => 0.1,
|
|
},
|
|
});
|
|
}
|
|
|
|
Ok(data_points)
|
|
}
|
|
|
|
/// Get comprehensive performance statistics
|
|
pub async fn get_performance_stats(&self) -> RiskPerformanceStats {
|
|
self.performance_tracker.get_stats().await
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// MOCK TYPES AND IMPLEMENTATIONS
|
|
// =============================================================================
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct TestOrder {
|
|
pub id: String,
|
|
pub symbol: String,
|
|
pub side: OrderSide,
|
|
pub quantity: Decimal,
|
|
pub price: Decimal,
|
|
pub order_type: OrderType,
|
|
pub timestamp: HardwareTimestamp,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct TestPortfolio {
|
|
pub total_value: Decimal,
|
|
pub positions: Vec<Position>,
|
|
}
|
|
|
|
impl TestPortfolio {
|
|
pub fn new(initial_value: Decimal) -> Self {
|
|
Self {
|
|
total_value: initial_value,
|
|
positions: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn add_position(&mut self, position: Position) {
|
|
self.positions.push(position);
|
|
}
|
|
|
|
pub fn clear_positions(&mut self) {
|
|
self.positions.clear();
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Position {
|
|
pub symbol: String,
|
|
pub quantity: Decimal,
|
|
pub average_price: Decimal,
|
|
pub current_price: Decimal,
|
|
pub unrealized_pnl: Decimal,
|
|
pub market_value: Decimal,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct MarketCondition {
|
|
pub win_rate: f64,
|
|
pub avg_win: f64,
|
|
pub avg_loss: f64,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum MarketRegime {
|
|
TrendingUp { strength: f64 },
|
|
TrendingDown { strength: f64 },
|
|
Sideways { volatility: f64 },
|
|
HighVolatility { volatility: f64 },
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct MarketDataPoint {
|
|
pub symbol: String,
|
|
pub timestamp: HardwareTimestamp,
|
|
pub price: Decimal,
|
|
pub volume: u64,
|
|
pub volatility: f64,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum RiskLevel {
|
|
Low,
|
|
Medium,
|
|
High,
|
|
Critical,
|
|
}
|
|
|
|
// Performance tracking
|
|
#[derive(Debug)]
|
|
pub struct RiskPerformanceTracker {
|
|
risk_check_latencies: RwLock<Vec<u64>>,
|
|
kelly_calculation_latencies: RwLock<Vec<u64>>,
|
|
monitoring_latencies: RwLock<Vec<u64>>,
|
|
var_calculation_latencies: RwLock<Vec<u64>>,
|
|
}
|
|
|
|
impl RiskPerformanceTracker {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
risk_check_latencies: RwLock::new(Vec::new()),
|
|
kelly_calculation_latencies: RwLock::new(Vec::new()),
|
|
monitoring_latencies: RwLock::new(Vec::new()),
|
|
var_calculation_latencies: RwLock::new(Vec::new()),
|
|
}
|
|
}
|
|
|
|
pub async fn record_risk_check_latency(&self, latency_us: u64) {
|
|
self.risk_check_latencies.write().await.push(latency_us);
|
|
}
|
|
|
|
pub async fn record_kelly_calculation(&self, latency_us: u64) {
|
|
self.kelly_calculation_latencies.write().await.push(latency_us);
|
|
}
|
|
|
|
pub async fn record_monitoring_cycle(&self, latency_us: u64) {
|
|
self.monitoring_latencies.write().await.push(latency_us);
|
|
}
|
|
|
|
pub async fn record_var_calculation(&self, latency_us: u64) {
|
|
self.var_calculation_latencies.write().await.push(latency_us);
|
|
}
|
|
|
|
pub async fn get_stats(&self) -> RiskPerformanceStats {
|
|
let risk_lats = self.risk_check_latencies.read().await;
|
|
let kelly_lats = self.kelly_calculation_latencies.read().await;
|
|
let monitor_lats = self.monitoring_latencies.read().await;
|
|
let var_lats = self.var_calculation_latencies.read().await;
|
|
|
|
RiskPerformanceStats {
|
|
avg_risk_check_latency_us: if !risk_lats.is_empty() {
|
|
risk_lats.iter().sum::<u64>() / risk_lats.len() as u64
|
|
} else { 0 },
|
|
avg_kelly_calculation_latency_us: if !kelly_lats.is_empty() {
|
|
kelly_lats.iter().sum::<u64>() / kelly_lats.len() as u64
|
|
} else { 0 },
|
|
avg_monitoring_latency_us: if !monitor_lats.is_empty() {
|
|
monitor_lats.iter().sum::<u64>() / monitor_lats.len() as u64
|
|
} else { 0 },
|
|
avg_var_calculation_latency_us: if !var_lats.is_empty() {
|
|
var_lats.iter().sum::<u64>() / var_lats.len() as u64
|
|
} else { 0 },
|
|
total_risk_checks: risk_lats.len(),
|
|
total_kelly_calculations: kelly_lats.len(),
|
|
total_monitoring_cycles: monitor_lats.len(),
|
|
total_var_calculations: var_lats.len(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RiskPerformanceStats {
|
|
pub avg_risk_check_latency_us: u64,
|
|
pub avg_kelly_calculation_latency_us: u64,
|
|
pub avg_monitoring_latency_us: u64,
|
|
pub avg_var_calculation_latency_us: u64,
|
|
pub total_risk_checks: usize,
|
|
pub total_kelly_calculations: usize,
|
|
pub total_monitoring_cycles: usize,
|
|
pub total_var_calculations: usize,
|
|
}
|
|
|
|
// Mock implementations for risk components
|
|
pub struct RiskManager;
|
|
pub struct PortfolioMonitor;
|
|
pub struct VarCalculator;
|
|
pub struct KellyOptimizer;
|
|
pub struct PositionLimiter;
|
|
pub struct KillSwitch;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RiskConfig {
|
|
pub max_portfolio_risk: f64,
|
|
pub var_confidence: f64,
|
|
pub kill_switch_threshold: f64,
|
|
pub max_position_concentration: f64,
|
|
}
|
|
|
|
impl Default for RiskConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
max_portfolio_risk: 0.05,
|
|
var_confidence: 0.95,
|
|
kill_switch_threshold: 0.02,
|
|
max_position_concentration: 0.1,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RiskAssessment {
|
|
pub approved: bool,
|
|
pub risk_score: f64,
|
|
pub reason: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct KellyResult {
|
|
pub optimal_fraction: f64,
|
|
pub expected_return: f64,
|
|
pub risk_metrics: HashMap<String, f64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PortfolioRiskStatus {
|
|
pub total_var: Decimal,
|
|
pub portfolio_beta: f64,
|
|
pub risk_level: RiskLevel,
|
|
pub concentration_metrics: HashMap<String, f64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct KillSwitchStatus {
|
|
pub should_activate: bool,
|
|
pub response_time_ms: u64,
|
|
pub trigger_reason: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct LiquidationResult {
|
|
pub positions_liquidated: usize,
|
|
pub liquidation_time_ms: u64,
|
|
pub total_value_liquidated: Decimal,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct VarResult {
|
|
pub daily_var: Decimal,
|
|
pub marginal_var: HashMap<String, Decimal>,
|
|
pub component_var: HashMap<String, Decimal>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RebalanceAnalysis {
|
|
pub needs_rebalancing: bool,
|
|
pub concentration_risk: f64,
|
|
pub recommended_trades: Vec<RebalanceTrade>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RebalanceTrade {
|
|
pub symbol: String,
|
|
pub action: String,
|
|
pub quantity: Decimal,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RebalanceExecution {
|
|
pub trades_executed: usize,
|
|
pub execution_time_ms: u64,
|
|
pub total_volume: Decimal,
|
|
}
|
|
|
|
// Mock implementations
|
|
impl RiskManager {
|
|
pub async fn new(_config: RiskConfig) -> Result<Self, String> {
|
|
Ok(Self)
|
|
}
|
|
|
|
pub async fn validate_order(&self, _order: &TestOrder) -> Result<RiskAssessment, String> {
|
|
tokio::time::sleep(Duration::from_micros(2000)).await; // 2ms simulation
|
|
|
|
Ok(RiskAssessment {
|
|
approved: true,
|
|
risk_score: 0.3,
|
|
reason: "Order approved by risk management".to_string(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl PortfolioMonitor {
|
|
pub async fn new(_initial_value: Decimal) -> Result<Self, String> {
|
|
Ok(Self)
|
|
}
|
|
|
|
pub async fn assess_portfolio_risk(&self) -> Result<PortfolioRiskStatus, String> {
|
|
tokio::time::sleep(Duration::from_micros(5000)).await; // 5ms simulation
|
|
|
|
Ok(PortfolioRiskStatus {
|
|
total_var: Decimal::new(25000_00, 2),
|
|
portfolio_beta: 1.2,
|
|
risk_level: RiskLevel::Medium,
|
|
concentration_metrics: HashMap::new(),
|
|
})
|
|
}
|
|
|
|
pub async fn analyze_rebalancing_needs(&self) -> Result<RebalanceAnalysis, String> {
|
|
Ok(RebalanceAnalysis {
|
|
needs_rebalancing: true,
|
|
concentration_risk: 0.9,
|
|
recommended_trades: vec![
|
|
RebalanceTrade {
|
|
symbol: "EURUSD".to_string(),
|
|
action: "SELL".to_string(),
|
|
quantity: Decimal::new(30000, 0),
|
|
},
|
|
],
|
|
})
|
|
}
|
|
|
|
pub async fn execute_rebalancing(&self, _trades: &[RebalanceTrade]) -> Result<RebalanceExecution, String> {
|
|
tokio::time::sleep(Duration::from_millis(100)).await; // 100ms simulation
|
|
|
|
Ok(RebalanceExecution {
|
|
trades_executed: 1,
|
|
execution_time_ms: 100,
|
|
total_volume: Decimal::new(30000, 0),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl VarCalculator {
|
|
pub async fn new(_confidence: f64) -> Result<Self, String> {
|
|
Ok(Self)
|
|
}
|
|
|
|
pub async fn calculate_portfolio_var(&self, _data: &[MarketDataPoint], _confidence: f64) -> Result<VarResult, String> {
|
|
tokio::time::sleep(Duration::from_millis(50)).await; // 50ms simulation
|
|
|
|
let mut marginal_var = HashMap::new();
|
|
marginal_var.insert("EURUSD".to_string(), Decimal::new(15000_00, 2));
|
|
marginal_var.insert("GBPUSD".to_string(), Decimal::new(8000_00, 2));
|
|
marginal_var.insert("USDJPY".to_string(), Decimal::new(12000_00, 2));
|
|
|
|
Ok(VarResult {
|
|
daily_var: Decimal::new(25000_00, 2),
|
|
marginal_var,
|
|
component_var: HashMap::new(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl KellyOptimizer {
|
|
pub async fn new() -> Result<Self, String> {
|
|
Ok(Self)
|
|
}
|
|
|
|
pub async fn calculate_optimal_size(
|
|
&self,
|
|
_symbol: &str,
|
|
win_rate: f64,
|
|
avg_win: f64,
|
|
avg_loss: f64,
|
|
_portfolio_value: Decimal,
|
|
) -> Result<KellyResult, String> {
|
|
tokio::time::sleep(Duration::from_millis(10)).await; // 10ms simulation
|
|
|
|
// Kelly formula: f = (bp - q) / b
|
|
// where b = odds, p = win rate, q = loss rate
|
|
let odds = avg_win / avg_loss;
|
|
let kelly_fraction = (odds * win_rate - (1.0 - win_rate)) / odds;
|
|
let optimal_fraction = kelly_fraction.max(0.0).min(0.25); // Cap at 25%
|
|
|
|
Ok(KellyResult {
|
|
optimal_fraction,
|
|
expected_return: win_rate * avg_win - (1.0 - win_rate) * avg_loss,
|
|
risk_metrics: HashMap::new(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl PositionLimiter {
|
|
pub async fn new(_max_size: f64) -> Result<Self, String> {
|
|
Ok(Self)
|
|
}
|
|
}
|
|
|
|
impl KillSwitch {
|
|
pub async fn new(_threshold: f64) -> Result<Self, String> {
|
|
Ok(Self)
|
|
}
|
|
|
|
pub async fn reset(&self) -> Result<(), String> {
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn evaluate_activation(&self, portfolio_state: &PortfolioRiskStatus) -> Result<KillSwitchStatus, String> {
|
|
let should_activate = matches!(portfolio_state.risk_level, RiskLevel::Critical);
|
|
|
|
Ok(KillSwitchStatus {
|
|
should_activate,
|
|
response_time_ms: 50,
|
|
trigger_reason: if should_activate {
|
|
"Critical risk level detected".to_string()
|
|
} else {
|
|
"Normal operation".to_string()
|
|
},
|
|
})
|
|
}
|
|
|
|
pub async fn execute_emergency_liquidation(&self) -> Result<LiquidationResult, String> {
|
|
tokio::time::sleep(Duration::from_millis(1000)).await; // 1s simulation
|
|
|
|
Ok(LiquidationResult {
|
|
positions_liquidated: 3,
|
|
liquidation_time_ms: 1000,
|
|
total_value_liquidated: Decimal::new(950000_00, 2),
|
|
})
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// INTEGRATION TESTS
|
|
// =============================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_trading_risk_pretrade_checks() -> TestResult<()> {
|
|
let config = TradingRiskIntegrationConfig::default();
|
|
let suite = TradingRiskIntegrationSuite::new(config).await?;
|
|
|
|
suite.test_pretrade_risk_checks().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_trading_risk_kelly_sizing() -> TestResult<()> {
|
|
let config = TradingRiskIntegrationConfig::default();
|
|
let suite = TradingRiskIntegrationSuite::new(config).await?;
|
|
|
|
suite.test_kelly_sizing_optimization().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_trading_risk_monitoring() -> TestResult<()> {
|
|
let config = TradingRiskIntegrationConfig::default();
|
|
let suite = TradingRiskIntegrationSuite::new(config).await?;
|
|
|
|
suite.test_realtime_risk_monitoring().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_trading_risk_kill_switch() -> TestResult<()> {
|
|
let config = TradingRiskIntegrationConfig::default();
|
|
let suite = TradingRiskIntegrationSuite::new(config).await?;
|
|
|
|
suite.test_kill_switch_activation().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_trading_risk_var_calculation() -> TestResult<()> {
|
|
let config = TradingRiskIntegrationConfig::default();
|
|
let suite = TradingRiskIntegrationSuite::new(config).await?;
|
|
|
|
suite.test_var_calculation_accuracy().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_trading_risk_rebalancing() -> TestResult<()> {
|
|
let config = TradingRiskIntegrationConfig::default();
|
|
let suite = TradingRiskIntegrationSuite::new(config).await?;
|
|
|
|
suite.test_portfolio_rebalancing().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Comprehensive Trading-Risk integration test runner
|
|
#[tokio::test]
|
|
async fn run_comprehensive_trading_risk_integration_tests() -> TestResult<()> {
|
|
println!("=== TRADING ↔ RISK MANAGEMENT INTEGRATION TEST SUITE ===");
|
|
|
|
let config = TradingRiskIntegrationConfig::default();
|
|
let suite = TradingRiskIntegrationSuite::new(config).await?;
|
|
|
|
let test_timeout = Duration::from_secs(180); // 3 minutes per test
|
|
|
|
// Run all risk integration tests with timeout protection
|
|
timeout(test_timeout, suite.test_pretrade_risk_checks()).await??;
|
|
timeout(test_timeout, suite.test_kelly_sizing_optimization()).await??;
|
|
timeout(test_timeout, suite.test_realtime_risk_monitoring()).await??;
|
|
timeout(test_timeout, suite.test_kill_switch_activation()).await??;
|
|
timeout(test_timeout, suite.test_var_calculation_accuracy()).await??;
|
|
timeout(test_timeout, suite.test_portfolio_rebalancing()).await??;
|
|
|
|
// Display final performance statistics
|
|
let stats = suite.get_performance_stats().await;
|
|
|
|
println!("=== TRADING ↔ RISK MANAGEMENT TEST RESULTS ===");
|
|
println!("✓ Pre-trade risk checks (position limits, VaR)");
|
|
println!("✓ Kelly sizing calculations for position sizing");
|
|
println!("✓ Real-time risk monitoring during trades");
|
|
println!("✓ Kill switch activation under stress");
|
|
println!("✓ VaR calculation accuracy across market regimes");
|
|
println!("✓ Portfolio rebalancing triggers and execution");
|
|
println!("");
|
|
println!("Performance Summary:");
|
|
println!(" Average Risk Check Latency: {}μs ({})", stats.avg_risk_check_latency_us, stats.total_risk_checks);
|
|
println!(" Average Kelly Calculation: {}μs ({})", stats.avg_kelly_calculation_latency_us, stats.total_kelly_calculations);
|
|
println!(" Average Monitoring Cycle: {}μs ({})", stats.avg_monitoring_latency_us, stats.total_monitoring_cycles);
|
|
println!(" Average VaR Calculation: {}μs ({})", stats.avg_var_calculation_latency_us, stats.total_var_calculations);
|
|
println!("");
|
|
println!("✓ ALL TRADING ↔ RISK MANAGEMENT INTEGRATION TESTS PASSED");
|
|
|
|
Ok(())
|
|
} |