Files
foxhunt/tests/integration/trading_risk_integration.rs
jgrusewski 6093eac7bf 🔧 Tonic 0.14 Upgrade: Auto-generated and build system changes
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>
2025-10-03 07:34:26 +02:00

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(&regime).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(())
}