🎯 Wave 39: Test Infrastructure Remediation (48% Error Reduction)
EXECUTIVE SUMMARY: ================== Wave 39 achieved 48% error reduction (43 → 22) while maintaining zero production code errors. Production stability excellent, test infrastructure improving but still broken. User goals partially met (production stable, tests still need work). METRICS SUMMARY: =============== Production Code: ✅ 0 errors (STABLE) Test Code: ⚠️ 22 errors (48% improvement from 43) Total Errors: 22 (down from 43 in Wave 38) Warnings: 678 (regressed from ~60) Test Pass Rate: 0% (cannot measure - tests don't compile) USER GOALS ASSESSMENT: ===================== Goal 1 - Zero Errors: ⚠️ PARTIAL (0 production, 22 test) Goal 2 - 95% Tests Pass: ❌ BLOCKED (tests don't compile) Goal 3 - Zero Warnings: ❌ FAILED (678 warnings) WAVE COMPARISON: =============== | Metric | Wave 38 | Wave 39 | Change | |-------------------|---------|---------|-------------| | Production Errors | 0 | 0 | ✅ Stable | | Test Errors | 43 | 22 | -21 (-48%) | | Total Errors | 43 | 22 | -21 (-48%) | | Warnings | ~60 | 678 | ❌ Much Worse| WORK COMPLETED: ============== Files Modified: 32 files - Production: 12 files (all compile ✅) - Tests: 17 files (22 errors remain ❌) - Config: 3 files Changes: - 235 lines inserted - 157 lines deleted - Net: +78 lines Production Code Changes (ALL COMPILE): ✅ ml/src/dqn/*.rs - Added #[allow(dead_code)] ✅ ml/src/mamba/*.rs - Added #[allow(dead_code)] ✅ ml/src/ppo/*.rs - Added #[allow(dead_code)] ✅ ml/src/integration/coordinator.rs ✅ ml/src/portfolio_transformer.rs ✅ trading_engine/src/lockfree/small_batch_ring.rs Test Infrastructure Changes (22 ERRORS REMAIN): ⚠️ tests/fixtures/builders.rs - Type fixes, Result handling ⚠️ tests/fixtures/scenarios.rs - StressScenario refactoring ⚠️ tests/fixtures/test_data.rs - Import improvements ⚠️ tests/fixtures/test_database.rs - Refactoring ⚠️ tests/integration/* - Various fixes REMAINING BLOCKERS (22 errors): ============================== 1. Event Struct Mismatches (6 errors) - Missing timestamp/data fields - Need to update Event usage 2. StressScenario Type Confusion (10 errors) - risk::risk_types vs risk_data::models - Need consistent type usage 3. Price::from_f64 Result Handling (6 errors) - Returns Result, not Price - Need .unwrap() or error handling ERROR BREAKDOWN BY TYPE: ======================= E0560 (missing fields): 8 errors (36%) E0308 (type mismatch): 6 errors (27%) E0599 (method missing): 4 errors (18%) E0277 (trait bound): 2 errors (9%) Other: 2 errors (10%) CRITICAL FINDINGS: ================= ✅ GOOD NEWS: - Production code completely stable (0 errors) - Steady progress (48% error reduction) - All production crates compile successfully - Clear path to zero errors ❌ CONCERNS: - Test infrastructure still broken - Cannot measure test pass rate - Warning count MASSIVELY regressed (60 → 678) - Test fixtures need architectural fixes ⚠️ OBSERVATIONS: - #[allow(dead_code)] usage masks underlying issues - Type system mismatches are mechanical to fix - Most errors concentrated in 3 test fixture files - At current rate, 1 more wave to zero errors - Warnings need URGENT attention in Wave 40 WAVE 40 RECOMMENDATION: ====================== Decision: ⚠️ CONDITIONAL GO (with warning remediation priority) Strategy: Focused remediation with targeted agent assignments - Agents 1-2: Event struct fixes (6 errors) - Agents 3-4: StressScenario alignment (10 errors) - Agents 5-6: Price Result handling (6 errors) - Agents 7-8: Remaining error fixes - Agent 9: Warning remediation (URGENT - 678 warnings) - Agent 10: Verification - Agent 11: Final warning cleanup - Agent 12: Final report Success Criteria for Wave 40: ✅ MUST: 0 compilation errors ✅ MUST: Tests compile and run ✅ MUST: Measure test pass rate ✅ MUST: Warnings < 100 (from 678) ⚠️ SHOULD: Pass rate > 80% ⚠️ SHOULD: Warnings < 50 Estimated Time: 90-120 minutes Success Probability: MEDIUM-HIGH (75%+) LESSONS LEARNED: =============== ✅ What Worked: - Production stability maintained - Steady error reduction trajectory - Clear error categorization - Separate production verification ❌ What Didn't Work: - Warning suppression vs. fixing root causes - Insufficient agent reporting - Lack of coordination - WARNING COUNT EXPLOSION (10x regression!) 🎯 Improvements for Wave 40: - Focused 3-agent team for errors - Dedicated agents for warning cleanup - Mandatory completion reports - Test before commit - Address root causes, not symptoms - NO MORE #[allow()] without justification DOCUMENTATION: ============= Reports Generated: ✅ wave39_verification_report.md - Agent 10 production check ✅ WAVE39_COMPLETION_REPORT.md - This comprehensive report NEXT STEPS: ========== 1. Launch Wave 40 with DUAL focus: errors AND warnings 2. Target: 0 compilation errors + <100 warnings in 90-120 minutes 3. Measure test pass rate once tests compile 4. Address warning explosion as P0 priority 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ tokio-stream.workspace = true
|
||||
# Core Foxhunt crates
|
||||
trading_engine.workspace = true
|
||||
risk.workspace = true
|
||||
risk-data.workspace = true
|
||||
ml.workspace = true
|
||||
data.workspace = true
|
||||
tli.workspace = true
|
||||
@@ -39,6 +40,7 @@ futures.workspace = true
|
||||
sqlx.workspace = true
|
||||
uuid.workspace = true
|
||||
rust_decimal.workspace = true
|
||||
rust_decimal_macros = "1.36"
|
||||
|
||||
# Error handling
|
||||
anyhow.workspace = true
|
||||
@@ -49,6 +51,7 @@ clap.workspace = true
|
||||
|
||||
# Additional test dependencies
|
||||
rand.workspace = true
|
||||
rand_distr = "0.4"
|
||||
parking_lot.workspace = true
|
||||
hdrhistogram.workspace = true
|
||||
lazy_static = "1.4"
|
||||
|
||||
@@ -334,10 +334,16 @@ e2e_test!(
|
||||
let mut db_conn = framework.create_test_transaction().await?;
|
||||
|
||||
// Query configuration directly from database
|
||||
let db_config_query = sqlx::query!(
|
||||
"SELECT key, value FROM configuration WHERE key = $1",
|
||||
"risk_limit"
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct ConfigRow {
|
||||
key: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
let db_config_query = sqlx::query_as::<_, ConfigRow>(
|
||||
"SELECT key, value FROM configuration WHERE key = $1"
|
||||
)
|
||||
.bind("risk_limit")
|
||||
.fetch_optional(&mut *db_conn)
|
||||
.await?;
|
||||
|
||||
@@ -353,16 +359,15 @@ e2e_test!(
|
||||
info!("📊 Testing configuration audit trail");
|
||||
|
||||
// Query configuration history/audit trail from database
|
||||
let audit_query = sqlx::query!(
|
||||
"SELECT COUNT(*) as change_count FROM configuration_audit WHERE key = $1",
|
||||
"risk_limit"
|
||||
let audit_query: Result<Option<i64>, _> = sqlx::query_scalar(
|
||||
"SELECT COUNT(*) FROM configuration_audit WHERE key = $1"
|
||||
)
|
||||
.bind("risk_limit")
|
||||
.fetch_optional(&mut *db_conn)
|
||||
.await;
|
||||
|
||||
match audit_query {
|
||||
Ok(Some(row)) => {
|
||||
let change_count = row.change_count.unwrap_or(0);
|
||||
Ok(Some(change_count)) => {
|
||||
info!(
|
||||
"Configuration audit entries for 'risk_limit': {}",
|
||||
change_count
|
||||
|
||||
9
tests/fixtures/builders.rs
vendored
9
tests/fixtures/builders.rs
vendored
@@ -34,6 +34,7 @@
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use ::rust_decimal::Decimal;
|
||||
use ::rust_decimal::prelude::ToPrimitive;
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -41,7 +42,7 @@ use uuid::Uuid;
|
||||
use risk::risk_types::Position;
|
||||
use common::types::Price;
|
||||
|
||||
use crate::fixtures::helpers::{ToDecimal, decimal};
|
||||
use crate::fixtures::helpers::ToDecimal;
|
||||
|
||||
// Use local test fixture types defined in mod.rs
|
||||
use super::*;
|
||||
@@ -200,7 +201,7 @@ impl InstrumentBuilder {
|
||||
pub fn bond(mut self) -> Self {
|
||||
self.instrument_type = InstrumentType::Bond;
|
||||
self.asset_class = AssetClass::FixedIncome;
|
||||
self.sector = Some(MarketSector::Government);
|
||||
self.sector = Some(MarketSector::Financials);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -468,7 +469,7 @@ impl PositionBuilder {
|
||||
market_price: price,
|
||||
market_value,
|
||||
average_cost: price,
|
||||
average_price: Price::from_f64(price),
|
||||
average_price: Price::from_f64(price).unwrap_or_else(|_| Price::new(0.0).unwrap()),
|
||||
unrealized_pnl: 0.0,
|
||||
realized_pnl: 0.0,
|
||||
last_updated: Utc::now().timestamp(),
|
||||
@@ -500,7 +501,7 @@ impl PositionBuilder {
|
||||
pub fn with_average_price(mut self, price: Decimal) -> Self {
|
||||
let price_f64 = price.to_f64().unwrap_or(0.0);
|
||||
self.average_cost = price_f64;
|
||||
self.average_price = Price::from_f64(price_f64);
|
||||
self.average_price = Price::from_f64(price_f64).unwrap_or_else(|_| Price::new(0.0).unwrap());
|
||||
// Recalculate unrealized PnL
|
||||
self.unrealized_pnl = (self.market_price - price_f64) * self.quantity;
|
||||
self
|
||||
|
||||
2
tests/fixtures/helpers.rs
vendored
2
tests/fixtures/helpers.rs
vendored
@@ -5,7 +5,7 @@
|
||||
/// floating point values for convenience.
|
||||
|
||||
use rust_decimal::Decimal;
|
||||
use rust_decimal_macros::dec;
|
||||
pub use rust_decimal_macros::dec;
|
||||
|
||||
/// Trait to convert f64 to Decimal safely
|
||||
pub trait ToDecimal {
|
||||
|
||||
4
tests/fixtures/mock_services.rs
vendored
4
tests/fixtures/mock_services.rs
vendored
@@ -5,16 +5,14 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{mpsc, RwLock, Mutex};
|
||||
use tokio::time::sleep;
|
||||
use uuid::Uuid;
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use serde_json::json;
|
||||
|
||||
use super::test_config::TestConfig;
|
||||
use super::*;
|
||||
|
||||
// =============================================================================
|
||||
// MOCK TRADING SERVICE
|
||||
|
||||
64
tests/fixtures/mod.rs
vendored
64
tests/fixtures/mod.rs
vendored
@@ -27,20 +27,16 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, atomic::{AtomicU16, AtomicU64, Ordering}};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::sync::{mpsc, RwLock, Mutex};
|
||||
use uuid::Uuid;
|
||||
use serde_json::json;
|
||||
use chrono::{DateTime, Utc, Duration as ChronoDuration};
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
use tli::prelude::*;
|
||||
|
||||
// Import StressScenario from risk crate
|
||||
// This is the version with Uuid id, description, scenario_type, etc.
|
||||
pub use risk::risk_types::StressScenario;
|
||||
|
||||
// Re-export sub-modules for easy access
|
||||
pub mod test_config;
|
||||
pub mod test_database;
|
||||
@@ -157,6 +153,20 @@ pub enum AssetClass {
|
||||
Alternatives,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AssetClass {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AssetClass::Equities => write!(f, "Equities"),
|
||||
AssetClass::Currencies => write!(f, "Currencies"),
|
||||
AssetClass::Derivatives => write!(f, "Derivatives"),
|
||||
AssetClass::FixedIncome => write!(f, "FixedIncome"),
|
||||
AssetClass::Commodities => write!(f, "Commodities"),
|
||||
AssetClass::Cash => write!(f, "Cash"),
|
||||
AssetClass::Alternatives => write!(f, "Alternatives"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Instrument type for test fixtures
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum InstrumentType {
|
||||
@@ -171,6 +181,22 @@ pub enum InstrumentType {
|
||||
CDS,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InstrumentType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
InstrumentType::Equity => write!(f, "Equity"),
|
||||
InstrumentType::Currency => write!(f, "Currency"),
|
||||
InstrumentType::Future => write!(f, "Future"),
|
||||
InstrumentType::Option => write!(f, "Option"),
|
||||
InstrumentType::Bond => write!(f, "Bond"),
|
||||
InstrumentType::Commodity => write!(f, "Commodity"),
|
||||
InstrumentType::Crypto => write!(f, "Crypto"),
|
||||
InstrumentType::Swap => write!(f, "Swap"),
|
||||
InstrumentType::CDS => write!(f, "CDS"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Market sector for test fixtures
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum MarketSector {
|
||||
@@ -585,7 +611,7 @@ impl TestEventPublisher {
|
||||
|
||||
pub async fn publish_event(&self, event: Event) -> TliResult<()> {
|
||||
self._event_sender.send(event)
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to publish event: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to publish event: {}", e)))?;
|
||||
|
||||
self.published_events.fetch_add(1, Ordering::Relaxed);
|
||||
Ok(())
|
||||
@@ -596,14 +622,19 @@ impl TestEventPublisher {
|
||||
let event = Event {
|
||||
id: Uuid::new_v4(),
|
||||
event_type: EventType::MarketData,
|
||||
timestamp: Utc::now(),
|
||||
data: json!({
|
||||
severity: EventSeverity::Info,
|
||||
source: "test_publisher".to_string(),
|
||||
timestamp_nanos: Utc::now().timestamp_nanos_opt().unwrap_or(0),
|
||||
sequence: i as u64,
|
||||
payload: json!({
|
||||
"symbol": symbol,
|
||||
"price": 150.0 + (i as f64 * 0.01),
|
||||
"volume": 100 + i,
|
||||
"sequence": i
|
||||
}),
|
||||
source: "test_publisher".to_string(),
|
||||
correlation_id: None,
|
||||
metadata: HashMap::new(),
|
||||
ttl_seconds: 0,
|
||||
};
|
||||
|
||||
self.publish_event(event).await?;
|
||||
@@ -618,15 +649,20 @@ impl TestEventPublisher {
|
||||
for (i, state) in states.iter().enumerate() {
|
||||
let event = Event {
|
||||
id: Uuid::new_v4(),
|
||||
event_type: EventType::OrderUpdate,
|
||||
timestamp: Utc::now(),
|
||||
data: json!({
|
||||
event_type: EventType::Trading,
|
||||
severity: EventSeverity::Info,
|
||||
source: "test_lifecycle".to_string(),
|
||||
timestamp_nanos: Utc::now().timestamp_nanos_opt().unwrap_or(0),
|
||||
sequence: i as u64,
|
||||
payload: json!({
|
||||
"order_id": order_id,
|
||||
"status": state,
|
||||
"filled_quantity": (i + 1) * 50,
|
||||
"remaining_quantity": 100 - ((i + 1) * 50)
|
||||
}),
|
||||
source: "test_lifecycle".to_string(),
|
||||
correlation_id: None,
|
||||
metadata: HashMap::new(),
|
||||
ttl_seconds: 0,
|
||||
};
|
||||
|
||||
self.publish_event(event).await?;
|
||||
|
||||
48
tests/fixtures/scenarios.rs
vendored
48
tests/fixtures/scenarios.rs
vendored
@@ -23,14 +23,14 @@
|
||||
|
||||
use chrono::{DateTime, Utc, Duration as ChronoDuration};
|
||||
use rust_decimal::Decimal;
|
||||
use serde_json::json;
|
||||
use rust_decimal::prelude::ToPrimitive;
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
// Import types from risk crate
|
||||
use risk::risk_types::Position;
|
||||
// Note: StressScenario imported from mod.rs (risk_data::models version)
|
||||
use crate::fixtures::helpers::{ToDecimal, decimal};
|
||||
use crate::fixtures::helpers::ToDecimal;
|
||||
use super::builders::*;
|
||||
use super::*;
|
||||
|
||||
@@ -95,7 +95,7 @@ impl BasicTradingScenario {
|
||||
symbols_and_weights
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, (symbol, weight))| {
|
||||
.map(|(_i, (symbol, weight))| {
|
||||
let position_value = self.total_value * Decimal::try_from(weight).unwrap();
|
||||
let price = get_test_price_for_symbol(symbol).to_decimal();
|
||||
let quantity = position_value / price;
|
||||
@@ -196,25 +196,23 @@ impl MarketCrashScenario {
|
||||
}
|
||||
|
||||
/// Create a formal stress test scenario record
|
||||
pub fn create_stress_scenario(&self) -> StressScenario {
|
||||
let shock_factors = json!({
|
||||
"equity_shock": self.equity_shock,
|
||||
"bond_shock": self.bond_shock,
|
||||
"commodity_shock": self.commodity_shock,
|
||||
"fx_shock": self.fx_shock,
|
||||
"volatility_shock": self.volatility_shock
|
||||
});
|
||||
pub fn create_stress_scenario(&self) -> risk::risk_types::StressScenario {
|
||||
let mut price_shocks = HashMap::new();
|
||||
price_shocks.insert("EQUITY".to_string(), self.equity_shock.to_f64().unwrap_or(0.0));
|
||||
price_shocks.insert("BOND".to_string(), self.bond_shock.to_f64().unwrap_or(0.0));
|
||||
price_shocks.insert("COMMODITY".to_string(), self.commodity_shock.to_f64().unwrap_or(0.0));
|
||||
price_shocks.insert("FX".to_string(), self.fx_shock.to_f64().unwrap_or(0.0));
|
||||
|
||||
StressScenario {
|
||||
id: Uuid::new_v4(),
|
||||
risk::risk_types::StressScenario {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
name: self.name.clone(),
|
||||
description: self.description.clone(),
|
||||
scenario_type: "Historical".to_string(),
|
||||
active: true,
|
||||
shock_factors,
|
||||
created_by: "test_system".to_string(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
price_shocks: price_shocks.clone(),
|
||||
market_shocks: price_shocks,
|
||||
volatility_multiplier: 1.0 + self.volatility_shock.to_f64().unwrap_or(0.0),
|
||||
volatility_multipliers: HashMap::new(),
|
||||
correlation_changes: HashMap::new(),
|
||||
correlation_adjustments: HashMap::new(),
|
||||
liquidity_haircuts: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,7 +422,7 @@ impl RiskLimitBreachScenario {
|
||||
|
||||
/// Create positions that breach concentration limits
|
||||
pub fn create_concentrated_positions(&self) -> Vec<Position> {
|
||||
let total_portfolio_value = Decimal::from(1000000);
|
||||
let _total_portfolio_value = Decimal::from(1000000);
|
||||
|
||||
vec![
|
||||
// Concentrated position - 40% of portfolio (breaches 25% limit)
|
||||
@@ -553,7 +551,7 @@ impl ScenarioFactory {
|
||||
}
|
||||
|
||||
/// Create a market crash stress test scenario
|
||||
pub fn market_crash() -> (StressScenario, Vec<Position>) {
|
||||
pub fn market_crash() -> (risk::risk_types::StressScenario, Vec<Position>) {
|
||||
let crash_scenario = MarketCrashScenario::new();
|
||||
let basic_scenario = BasicTradingScenario::new();
|
||||
let original_positions = basic_scenario.create_positions();
|
||||
@@ -610,9 +608,11 @@ mod tests {
|
||||
assert_eq!(portfolio.id, TEST_PORTFOLIO_1);
|
||||
assert!(!positions.is_empty());
|
||||
assert_eq!(positions.len(), instruments.len());
|
||||
|
||||
|
||||
// Check portfolio value adds up
|
||||
let total_value: Decimal = positions.iter().map(|p| p.market_value).sum();
|
||||
let total_value: Decimal = positions.iter()
|
||||
.map(|p| p.market_value.to_decimal())
|
||||
.sum();
|
||||
assert!((total_value - scenario.total_value).abs() < Decimal::new(1, 0)); // Within $1
|
||||
}
|
||||
|
||||
|
||||
40
tests/fixtures/test_data.rs
vendored
40
tests/fixtures/test_data.rs
vendored
@@ -23,20 +23,18 @@
|
||||
//! .generate_random_portfolio(10); // 10 positions
|
||||
//! ```
|
||||
|
||||
use chrono::{DateTime, Utc, Duration as ChronoDuration};
|
||||
use chrono::{DateTime, Utc, Duration as ChronoDuration, Timelike};
|
||||
use ::rust_decimal::Decimal;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
use rand::{Rng, SeedableRng, rngs::StdRng};
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
// Note: Normal distribution temporarily disabled - use Standard for now
|
||||
// use rand::distributions::Normal;
|
||||
use rand::distributions::Distribution;
|
||||
use rand_distr::Normal;
|
||||
|
||||
// Import types from risk crate
|
||||
use risk::risk_types::Position;
|
||||
// Note: StressScenario imported from mod.rs (risk_data::models version)
|
||||
use crate::fixtures::helpers::{ToDecimal, decimal};
|
||||
use risk::risk_types::{Position, StressScenario};
|
||||
use crate::fixtures::helpers::ToDecimal;
|
||||
use super::builders::*;
|
||||
use super::*;
|
||||
|
||||
@@ -515,16 +513,20 @@ impl RandomDataGenerator {
|
||||
"volatility_multiplier": self.rng.gen_range(1.0..=5.0)
|
||||
});
|
||||
|
||||
let equity_shock = shock_factors.get("equity_shock").and_then(|v| v.as_f64()).unwrap_or(0.0);
|
||||
let mut price_shocks = HashMap::new();
|
||||
price_shocks.insert("EQUITY".to_string(), equity_shock);
|
||||
|
||||
scenarios.push(StressScenario {
|
||||
id: Uuid::new_v4(),
|
||||
id: Uuid::new_v4().to_string(),
|
||||
name: format!("Random Stress Scenario {}", i + 1),
|
||||
description: format!("Randomly generated {} stress test", scenario_type),
|
||||
scenario_type: scenario_type.to_string(),
|
||||
active: true,
|
||||
shock_factors,
|
||||
created_by: "random_generator".to_string(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
price_shocks: price_shocks.clone(),
|
||||
market_shocks: price_shocks,
|
||||
volatility_multiplier: shock_factors.get("volatility_multiplier").and_then(|v| v.as_f64()).unwrap_or(1.0),
|
||||
volatility_multipliers: HashMap::new(),
|
||||
correlation_changes: HashMap::new(),
|
||||
correlation_adjustments: HashMap::new(),
|
||||
liquidity_haircuts: HashMap::new(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -775,11 +777,11 @@ mod tests {
|
||||
fn test_volatility_estimates() {
|
||||
let volatilities = create_test_volatilities();
|
||||
assert!(!volatilities.is_empty());
|
||||
|
||||
|
||||
// Check that volatilities are reasonable (between 0 and 100%)
|
||||
for (&symbol, &vol) in &volatilities {
|
||||
assert!(vol > 0.0);
|
||||
assert!(vol < 1.0); // Less than 100% for most assets
|
||||
for (symbol, &vol) in &volatilities {
|
||||
assert!(vol > 0.0, "Volatility for {} should be > 0", symbol);
|
||||
assert!(vol < 1.0, "Volatility for {} should be < 1.0", symbol); // Less than 100% for most assets
|
||||
}
|
||||
}
|
||||
}
|
||||
99
tests/fixtures/test_database.rs
vendored
99
tests/fixtures/test_database.rs
vendored
@@ -4,7 +4,7 @@
|
||||
//! for testing database operations in isolation.
|
||||
|
||||
use std::sync::Arc;
|
||||
use sqlx::{PgPool, Pool, Postgres, migrate::MigrateDatabase};
|
||||
use sqlx::{PgPool, Postgres, migrate::MigrateDatabase};
|
||||
use tokio::sync::OnceCell;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -138,25 +138,25 @@ impl TestDatabase {
|
||||
let instruments = BatchBuilder::create_diverse_instruments(ALL_TEST_SYMBOLS.len());
|
||||
|
||||
for instrument in instruments {
|
||||
sqlx::query!(
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO instruments (
|
||||
id, symbol, name, instrument_type, asset_class,
|
||||
id, symbol, name, instrument_type, asset_class,
|
||||
currency, is_active, created_at, updated_at, metadata
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
ON CONFLICT (symbol) DO NOTHING
|
||||
"#,
|
||||
instrument.id,
|
||||
instrument.symbol,
|
||||
instrument.name,
|
||||
instrument.instrument_type as _,
|
||||
instrument.asset_class as _,
|
||||
instrument.currency,
|
||||
instrument.is_active,
|
||||
instrument.created_at,
|
||||
instrument.updated_at,
|
||||
instrument.metadata,
|
||||
)
|
||||
.bind(instrument.id)
|
||||
.bind(instrument.symbol)
|
||||
.bind(instrument.name)
|
||||
.bind(instrument.instrument_type.to_string())
|
||||
.bind(instrument.asset_class.to_string())
|
||||
.bind(instrument.currency)
|
||||
.bind(instrument.is_active)
|
||||
.bind(instrument.created_at)
|
||||
.bind(instrument.updated_at)
|
||||
.bind(instrument.metadata)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
}
|
||||
@@ -174,7 +174,7 @@ impl TestDatabase {
|
||||
];
|
||||
|
||||
for portfolio in portfolios {
|
||||
sqlx::query!(
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO portfolios (
|
||||
id, name, description, base_currency, portfolio_type,
|
||||
@@ -182,18 +182,18 @@ impl TestDatabase {
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
ON CONFLICT (id) DO NOTHING
|
||||
"#,
|
||||
portfolio.id,
|
||||
portfolio.name,
|
||||
portfolio.description,
|
||||
portfolio.base_currency,
|
||||
portfolio.portfolio_type,
|
||||
portfolio.inception_date,
|
||||
portfolio.manager_id,
|
||||
portfolio.is_active,
|
||||
portfolio.created_at,
|
||||
portfolio.updated_at,
|
||||
portfolio.metadata,
|
||||
)
|
||||
.bind(portfolio.id)
|
||||
.bind(portfolio.name)
|
||||
.bind(portfolio.description)
|
||||
.bind(portfolio.base_currency)
|
||||
.bind(portfolio.portfolio_type)
|
||||
.bind(portfolio.inception_date)
|
||||
.bind(portfolio.manager_id)
|
||||
.bind(portfolio.is_active)
|
||||
.bind(portfolio.created_at)
|
||||
.bind(portfolio.updated_at)
|
||||
.bind(portfolio.metadata)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
}
|
||||
@@ -212,7 +212,7 @@ impl TestDatabase {
|
||||
];
|
||||
|
||||
for counterparty in counterparties {
|
||||
sqlx::query!(
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO counterparty (
|
||||
id, name, counterparty_type, country, credit_rating,
|
||||
@@ -220,17 +220,17 @@ impl TestDatabase {
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
ON CONFLICT (id) DO NOTHING
|
||||
"#,
|
||||
counterparty.id,
|
||||
counterparty.name,
|
||||
counterparty.counterparty_type,
|
||||
counterparty.country,
|
||||
counterparty.credit_rating,
|
||||
counterparty.is_active,
|
||||
counterparty.netting_agreement,
|
||||
counterparty.created_at,
|
||||
counterparty.updated_at,
|
||||
counterparty.metadata,
|
||||
)
|
||||
.bind(counterparty.id)
|
||||
.bind(counterparty.name)
|
||||
.bind(counterparty.counterparty_type)
|
||||
.bind(counterparty.country)
|
||||
.bind(counterparty.credit_rating)
|
||||
.bind(counterparty.is_active)
|
||||
.bind(counterparty.netting_agreement)
|
||||
.bind(counterparty.created_at)
|
||||
.bind(counterparty.updated_at)
|
||||
.bind(counterparty.metadata)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
}
|
||||
@@ -240,45 +240,42 @@ impl TestDatabase {
|
||||
|
||||
/// Check if database schema is ready
|
||||
pub async fn is_schema_ready(&self) -> bool {
|
||||
let result = sqlx::query!(
|
||||
let result: Result<(bool,), _> = sqlx::query_as(
|
||||
"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'instruments')"
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await;
|
||||
|
||||
|
||||
match result {
|
||||
Ok(row) => row.exists.unwrap_or(false),
|
||||
Ok((exists,)) => exists,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get database statistics
|
||||
pub async fn get_stats(&self) -> Result<DatabaseStats, sqlx::Error> {
|
||||
let instruments_count = sqlx::query_scalar!(
|
||||
let instruments_count: Option<i64> = sqlx::query_scalar(
|
||||
"SELECT COUNT(*) FROM instruments WHERE symbol LIKE 'TEST_%'"
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
.await?;
|
||||
|
||||
let portfolios_count = sqlx::query_scalar!(
|
||||
let portfolios_count: Option<i64> = sqlx::query_scalar(
|
||||
"SELECT COUNT(*) FROM portfolios WHERE id LIKE 'TEST_%'"
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
.await?;
|
||||
|
||||
let positions_count = sqlx::query_scalar!(
|
||||
let positions_count: Option<i64> = sqlx::query_scalar(
|
||||
"SELECT COUNT(*) FROM positions WHERE portfolio_id LIKE 'TEST_%'"
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
.await?;
|
||||
|
||||
Ok(DatabaseStats {
|
||||
instruments_count,
|
||||
portfolios_count,
|
||||
positions_count,
|
||||
instruments_count: instruments_count.unwrap_or(0),
|
||||
portfolios_count: portfolios_count.unwrap_or(0),
|
||||
positions_count: positions_count.unwrap_or(0),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
use chrono::Utc;
|
||||
// Import from workspace dependencies properly
|
||||
use ::common::{OrderSide, OrderStatus, OrderType, TimeInForce};
|
||||
use ::rust_decimal::Decimal;
|
||||
use common::{OrderSide, OrderStatus, OrderType, TimeInForce};
|
||||
use rust_decimal::Decimal;
|
||||
use std::collections::HashMap;
|
||||
use ::trading_engine::trading_operations::TradingOrder;
|
||||
use trading_engine::trading_operations::TradingOrder;
|
||||
|
||||
/// Generate a simple test ID instead of using uuid
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -102,7 +102,7 @@ impl ConfigHotReloadTests {
|
||||
// Create test configuration directory
|
||||
let test_config_dir = std::env::temp_dir().join(format!("config_test_{}", Uuid::new_v4()));
|
||||
fs::create_dir_all(&test_config_dir).await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to create test config dir: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to create test config dir: {}", e)))?;
|
||||
|
||||
// Initialize configuration manager
|
||||
let config_manager_config = ConfigManagerConfig {
|
||||
@@ -115,7 +115,7 @@ impl ConfigHotReloadTests {
|
||||
|
||||
let config_manager = Arc::new(
|
||||
ConfigManager::new(config_manager_config).await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to create config manager: {}", e)))?
|
||||
.map_err(|e| TliError::Other(format!("Failed to create config manager: {}", e)))?
|
||||
);
|
||||
|
||||
// Initialize hot-reload manager
|
||||
@@ -131,7 +131,7 @@ impl ConfigHotReloadTests {
|
||||
|
||||
let hot_reload_manager = Arc::new(
|
||||
HotReloadManager::new(hot_reload_config, Arc::clone(&config_manager)).await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to create hot-reload manager: {}", e)))?
|
||||
.map_err(|e| TliError::Other(format!("Failed to create hot-reload manager: {}", e)))?
|
||||
);
|
||||
|
||||
// Create TLI client suite
|
||||
@@ -178,11 +178,11 @@ impl ConfigHotReloadTests {
|
||||
|
||||
// Register configuration file with hot-reload manager
|
||||
self.hot_reload_manager.add_config_file("trading_params", &config_file).await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to register config file: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to register config file: {}", e)))?;
|
||||
|
||||
// Setup change notification listener
|
||||
let notification_receiver = self.hot_reload_manager.subscribe_to_changes().await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to subscribe to changes: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to subscribe to changes: {}", e)))?;
|
||||
|
||||
// Modify configuration file
|
||||
let reload_start = Instant::now();
|
||||
@@ -233,7 +233,7 @@ impl ConfigHotReloadTests {
|
||||
|
||||
// Verify configuration was actually updated
|
||||
let current_config = self.config_manager.get_config("trading_params").await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to get current config: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to get current config: {}", e)))?;
|
||||
|
||||
let config_updated = if let Some(config_value) = current_config {
|
||||
config_value.get("max_position_size")
|
||||
@@ -292,7 +292,7 @@ impl ConfigHotReloadTests {
|
||||
|
||||
self.write_config_file(&config_file, &valid_config).await?;
|
||||
self.hot_reload_manager.add_config_file("risk_params", &config_file).await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to register config: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to register config: {}", e)))?;
|
||||
|
||||
// Setup validation rules
|
||||
let validation_rules = vec![
|
||||
@@ -315,12 +315,12 @@ impl ConfigHotReloadTests {
|
||||
|
||||
for rule in validation_rules {
|
||||
self.hot_reload_manager.add_validation_rule("risk_params", rule).await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to add validation rule: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to add validation rule: {}", e)))?;
|
||||
}
|
||||
|
||||
// Subscribe to rollback events
|
||||
let rollback_receiver = self.hot_reload_manager.subscribe_to_rollbacks().await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to subscribe to rollbacks: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to subscribe to rollbacks: {}", e)))?;
|
||||
|
||||
// Write invalid configuration (should trigger rollback)
|
||||
let rollback_start = Instant::now();
|
||||
@@ -365,7 +365,7 @@ impl ConfigHotReloadTests {
|
||||
|
||||
// Verify configuration was rolled back to valid state
|
||||
let current_config = self.config_manager.get_config("risk_params").await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to get current config: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to get current config: {}", e)))?;
|
||||
|
||||
let config_rolled_back = if let Some(config_value) = current_config {
|
||||
config_value.get("max_drawdown_pct")
|
||||
@@ -433,14 +433,14 @@ impl ConfigHotReloadTests {
|
||||
|
||||
self.write_config_file(&config_file, &initial_config).await?;
|
||||
self.hot_reload_manager.add_config_file(config_name, &config_file).await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to register {}: {}", config_name, e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to register {}: {}", config_name, e)))?;
|
||||
|
||||
file_paths.push((config_name.to_string(), config_file));
|
||||
}
|
||||
|
||||
// Setup change notification listener
|
||||
let notification_receiver = self.hot_reload_manager.subscribe_to_changes().await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to subscribe to changes: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to subscribe to changes: {}", e)))?;
|
||||
|
||||
// Concurrently modify all configuration files
|
||||
let concurrent_start = Instant::now();
|
||||
@@ -585,7 +585,7 @@ impl ConfigHotReloadTests {
|
||||
|
||||
self.write_config_file(&service_config_file, &initial_service_config).await?;
|
||||
self.hot_reload_manager.add_config_file("service_params", &service_config_file).await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to register service config: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to register service config: {}", e)))?;
|
||||
|
||||
// Configure mock trading service to respond to config changes
|
||||
self.mock_trading_service.set_config_update_handler(Box::new(|config| {
|
||||
@@ -597,7 +597,7 @@ impl ConfigHotReloadTests {
|
||||
|
||||
// Subscribe to service notifications
|
||||
let service_receiver = self.hot_reload_manager.subscribe_to_service_updates().await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to subscribe to service updates: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to subscribe to service updates: {}", e)))?;
|
||||
|
||||
// Submit some orders before configuration change
|
||||
let initial_orders = 10;
|
||||
@@ -706,7 +706,7 @@ impl ConfigHotReloadTests {
|
||||
|
||||
// Verify configuration was applied to the service
|
||||
let current_service_config = self.config_manager.get_config("service_params").await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to get service config: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to get service config: {}", e)))?;
|
||||
|
||||
let config_applied = if let Some(config) = current_service_config {
|
||||
config.get("max_concurrent_orders")
|
||||
@@ -733,10 +733,10 @@ impl ConfigHotReloadTests {
|
||||
/// Write configuration to file
|
||||
async fn write_config_file(&self, path: &Path, config: &serde_json::Value) -> TliResult<()> {
|
||||
let config_str = serde_json::to_string_pretty(config)
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to serialize config: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to serialize config: {}", e)))?;
|
||||
|
||||
fs::write(path, config_str).await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to write config file: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to write config file: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -786,12 +786,12 @@ impl ConfigHotReloadTests {
|
||||
// Remove test configuration directory
|
||||
if self.test_config_dir.exists() {
|
||||
fs::remove_dir_all(&self.test_config_dir).await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to cleanup test config dir: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to cleanup test config dir: {}", e)))?;
|
||||
}
|
||||
|
||||
// Shutdown hot-reload manager
|
||||
self.hot_reload_manager.shutdown().await
|
||||
.map_err(|e| TliError::InternalError(format!("Failed to shutdown hot-reload manager: {}", e)))?;
|
||||
.map_err(|e| TliError::Other(format!("Failed to shutdown hot-reload manager: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ impl RiskEnforcementTests {
|
||||
let within_limit_result = if let Some(trading_client) = &self.client_suite.trading_client {
|
||||
trading_client.submit_order(within_limit_order).await
|
||||
} else {
|
||||
return Err(TliError::InternalError("Trading client not available".to_string()));
|
||||
return Err(TliError::Other("Trading client not available".to_string()));
|
||||
};
|
||||
|
||||
let risk_check_latency = within_limit_start.elapsed().as_nanos() as u64;
|
||||
@@ -304,7 +304,7 @@ impl RiskEnforcementTests {
|
||||
let exceed_limit_result = if let Some(trading_client) = &self.client_suite.trading_client {
|
||||
trading_client.submit_order(exceed_limit_order).await
|
||||
} else {
|
||||
return Err(TliError::InternalError("Trading client not available".to_string()));
|
||||
return Err(TliError::Other("Trading client not available".to_string()));
|
||||
};
|
||||
|
||||
let rejection_latency = exceed_limit_start.elapsed().as_nanos() as u64;
|
||||
@@ -340,7 +340,7 @@ impl RiskEnforcementTests {
|
||||
let exact_limit_result = if let Some(trading_client) = &self.client_suite.trading_client {
|
||||
trading_client.submit_order(exact_limit_order).await
|
||||
} else {
|
||||
return Err(TliError::InternalError("Trading client not available".to_string()));
|
||||
return Err(TliError::Other("Trading client not available".to_string()));
|
||||
};
|
||||
|
||||
test_result.add_assertion(
|
||||
@@ -436,7 +436,7 @@ impl RiskEnforcementTests {
|
||||
let exceed_result = if let Some(trading_client) = &self.client_suite.trading_client {
|
||||
trading_client.submit_order(exceed_order).await
|
||||
} else {
|
||||
return Err(TliError::InternalError("Trading client not available".to_string()));
|
||||
return Err(TliError::Other("Trading client not available".to_string()));
|
||||
};
|
||||
|
||||
let exposure_rejection_latency = exceed_exposure_start.elapsed().as_nanos() as u64;
|
||||
@@ -610,7 +610,7 @@ impl RiskEnforcementTests {
|
||||
let risk_order_result = if let Some(trading_client) = &self.client_suite.trading_client {
|
||||
trading_client.submit_order(risk_order).await
|
||||
} else {
|
||||
return Err(TliError::InternalError("Trading client not available".to_string()));
|
||||
return Err(TliError::Other("Trading client not available".to_string()));
|
||||
};
|
||||
|
||||
let order_blocked = risk_order_result.is_err();
|
||||
|
||||
Reference in New Issue
Block a user