diff --git a/adaptive-strategy/src/regime/mod.rs b/adaptive-strategy/src/regime/mod.rs index 5b8d7982e..1e5d68989 100644 --- a/adaptive-strategy/src/regime/mod.rs +++ b/adaptive-strategy/src/regime/mod.rs @@ -248,9 +248,8 @@ pub struct RegimePerformanceTracker { regime_performance: HashMap, /// Detection accuracy tracking detection_accuracy: VecDeque, - /// False positive tracking - -} + /// False positive tracking metrics + false_positives: VecDeque, } /// Performance metrics for a specific regime #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/database/src/lib.rs b/database/src/lib.rs index 29252519d..4262decce 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -60,6 +60,8 @@ use crate::transaction::{DatabaseTransaction, TransactionManager, TransactionSta use crate::query::QueryBuilder; use config::database::DatabaseConfig; +// Re-export commonly used types - Already imported above, no need to re-export + // serde imports removed - not needed use sqlx::postgres::PgRow; use sqlx::FromRow; diff --git a/ml-data/src/features.rs b/ml-data/src/features.rs index cbf9e5399..8a8dbbdac 100644 --- a/ml-data/src/features.rs +++ b/ml-data/src/features.rs @@ -302,7 +302,7 @@ impl FeatureRepository { let conn = self.pool.get().await?; // Using sqlx query builder pattern - let query = + let (query, params) = if let Some(version) = feature_set_version { (r#"SELECT features, last_updated, expires_at FROM ml_feature_cache @@ -347,40 +347,37 @@ impl FeatureRepository { WHERE feature_set_id = $1 "#.to_string(); - // Using sqlx query builder pattern instead of raw parameters + // Using sqlx query builder pattern let mut query_builder = sqlx::QueryBuilder::new( "SELECT feature_name, feature_value, computation_timestamp FROM ml_feature_vectors WHERE feature_set_id = " ); query_builder.push_bind(feature_set_id); - let mut param_count = 1; // Add entity filter if !entity_ids.is_empty() { - param_count += 1; - query.push_str(&format!(" AND entity_id = ANY(${}) ", param_count)); - params.push(&entity_ids); + query_builder.push(" AND entity_id = ANY("); + query_builder.push_bind(&entity_ids); + query_builder.push(")"); } // Add time range filter if let Some((start, end)) = time_range { - param_count += 1; - query.push_str(&format!(" AND timestamp >= ${} ", param_count)); - params.push(&start); - param_count += 1; - query.push_str(&format!(" AND timestamp <= ${} ", param_count)); - params.push(&end); + query_builder.push(" AND timestamp >= "); + query_builder.push_bind(start); + query_builder.push(" AND timestamp <= "); + query_builder.push_bind(end); } - query.push_str(" ORDER BY timestamp DESC"); + query_builder.push(" ORDER BY timestamp DESC"); // Add limit if let Some(limit_val) = limit { - param_count += 1; - query.push_str(&format!(" LIMIT ${}", param_count)); - params.push(&(limit_val as i64)); - } - - let rows = conn.query(&query, ¶ms).await?; + query_builder.push(" LIMIT "); + query_builder.push_bind(limit_val as i64); + } + + let query = query_builder.build(); + let rows = query.fetch_all(&mut *conn).await?; let mut results = Vec::new(); for row in rows { diff --git a/ml-data/src/performance.rs b/ml-data/src/performance.rs index 9b5c365dc..2a815c350 100644 --- a/ml-data/src/performance.rs +++ b/ml-data/src/performance.rs @@ -219,40 +219,37 @@ impl PerformanceRepository { WHERE model_id = $1 "#.to_string(); - // Using sqlx query builder pattern instead of raw parameters + // Using sqlx query builder pattern let mut query_builder = sqlx::QueryBuilder::new( "SELECT timestamp, metric_name, metric_value, metric_metadata FROM ml_model_performance WHERE model_id = " ); query_builder.push_bind(model_id); - let mut param_count = 1; // Add metric name filter if let Some(ref names) = metric_names { - param_count += 1; - query.push_str(&format!(" AND metric_name = ANY(${}) ", param_count)); - params.push(names); + query_builder.push(" AND metric_name = ANY("); + query_builder.push_bind(names); + query_builder.push(")"); } // Add time range filter if let Some((start, end)) = time_range { - param_count += 1; - query.push_str(&format!(" AND timestamp >= ${} ", param_count)); - params.push(&start); - param_count += 1; - query.push_str(&format!(" AND timestamp <= ${} ", param_count)); - params.push(&end); + query_builder.push(" AND timestamp >= "); + query_builder.push_bind(start); + query_builder.push(" AND timestamp <= "); + query_builder.push_bind(end); } - query.push_str(" ORDER BY timestamp DESC"); + query_builder.push(" ORDER BY timestamp DESC"); // Add limit if let Some(limit_val) = limit { - param_count += 1; - query.push_str(&format!(" LIMIT ${}", param_count)); - params.push(&(limit_val as i64)); + query_builder.push(" LIMIT "); + query_builder.push_bind(limit_val as i64); } - let rows = conn.query(&query, ¶ms).await?; + let query = query_builder.build(); + let rows = query.fetch_all(&mut *conn).await?; let mut metrics = Vec::new(); for row in rows { @@ -418,22 +415,21 @@ impl PerformanceRepository { // Using sqlx query builder pattern let query = - if let Some(model_id) = model_id { - (r#"SELECT id, model_id, model_name, alert_type, severity, metric_name, - threshold_value, actual_value, triggered_at, message, metadata - FROM ml_performance_alerts - WHERE model_id = $1 AND status = 'active' - ORDER BY triggered_at DESC"#.to_string(), - vec![&model_id]) - } else { - (r#"SELECT id, model_id, model_name, alert_type, severity, metric_name, - threshold_value, actual_value, triggered_at, message, metadata - FROM ml_performance_alerts - WHERE status = 'active' - ORDER BY triggered_at DESC"#.to_string(), - vec![]) - }; - + let (query, params) = if let Some(model_id) = model_id { + (r#"SELECT id, model_id, model_name, alert_type, severity, metric_name, + threshold_value, actual_value, triggered_at, message, metadata + FROM ml_performance_alerts + WHERE model_id = $1 AND status = 'active' + ORDER BY triggered_at DESC"#.to_string(), + vec![&model_id]) + } else { + (r#"SELECT id, model_id, model_name, alert_type, severity, metric_name, + threshold_value, actual_value, triggered_at, message, metadata + FROM ml_performance_alerts + WHERE status = 'active' + ORDER BY triggered_at DESC"#.to_string(), + vec![]) + }; let rows = conn.query(&query, ¶ms).await?; let mut alerts = Vec::new(); diff --git a/risk/src/compliance.rs b/risk/src/compliance.rs index 7bf31d214..b29c990f7 100644 --- a/risk/src/compliance.rs +++ b/risk/src/compliance.rs @@ -1043,8 +1043,7 @@ impl ComplianceValidator { // CRITICAL: Market abuse thresholds must be configurable, not hardcoded // Different markets have different reporting thresholds - hardcoding could cause regulatory violations let threshold = self.config.market_abuse_threshold - .ok_or_else(|| RiskError::ConfigurationError { - parameter: "market_abuse_threshold".to_owned(), + .ok_or_else(|| RiskError::Configuration { message: "Market abuse threshold not configured - required for regulatory compliance".to_owned(), })?; if order_value > threshold { @@ -1256,7 +1255,7 @@ impl ComplianceValidator { order: &OrderInfo, violations: &[RiskViolation], warnings: &[ComplianceWarning], - ) -> Result { + ) -> Result { let mut risk_score = Price::ZERO; // Base risk from order size - use safe conversion helpers @@ -1280,7 +1279,7 @@ impl ComplianceValidator { let order_risk = f64_to_price_safe(order_value_f64 / 100_000.0, "order risk calculation") .map_err(|e| { error!("CRITICAL: Failed to calculate order risk - this could hide compliance violations: {}", e); - ComplianceError::ConversionError(format!("Failed to calculate order risk: {}", e)) + RiskError::Calculation { operation: "order_risk_calculation".to_string(), reason: format!("Failed to calculate order risk: {}", e) } })?; let current_risk_f64 = decimal_to_f64_safe( risk_score.to_decimal().unwrap_or(Decimal::ZERO), @@ -1309,7 +1308,7 @@ impl ComplianceValidator { f64_to_price_safe((violations.len() * 10) as f64, "violation risk calculation") .map_err(|e| { error!("CRITICAL: Failed to calculate violation risk - this could hide compliance issues: {}", e); - ComplianceError::ConversionError(format!("Failed to calculate violation risk: {}", e)) + RiskError::Calculation { operation: "violation_risk_calculation".to_string(), reason: format!("Failed to calculate violation risk: {}", e) } })?; let violation_risk_f64 = decimal_to_f64_safe( violation_risk.to_decimal().unwrap_or(Decimal::ZERO), diff --git a/risk/src/error.rs b/risk/src/error.rs index 6145aed4a..787d14cb5 100644 --- a/risk/src/error.rs +++ b/risk/src/error.rs @@ -325,6 +325,15 @@ pub enum RiskError { /// Market data system error occurred #[error("Market data error: {0}")] MarketDataError(String), + + /// Required data is unavailable for calculations + #[error("Data unavailable: {resource} - {reason}")] + DataUnavailable { + /// The data resource that is unavailable + resource: String, + /// Reason why the data is unavailable + reason: String, + }, } /// Result type for risk management operations @@ -500,8 +509,9 @@ impl RiskError { RiskError::BrokerConnection { .. } => "BROKER_CONNECTION_ERROR", RiskError::Connection { .. } => "CONNECTION_ERROR", RiskError::MarketDataError(_) => "MARKET_DATA_ERROR", + RiskError::DataUnavailable { .. } => "DATA_UNAVAILABLE", } - } + } } /// Convert from tokio timeout error diff --git a/risk/src/position_tracker.rs b/risk/src/position_tracker.rs index 27f1dadb6..2eccdbf12 100644 --- a/risk/src/position_tracker.rs +++ b/risk/src/position_tracker.rs @@ -1404,18 +1404,18 @@ impl PositionTracker { position.base_position.unrealized_pnl = Price::from_f64( ToPrimitive::to_f64(&unrealized_pnl) .ok_or_else(|| RiskError::TypeConversion { - from: "Decimal".to_string(), - to: "f64".to_string(), - value: unrealized_pnl.to_string(), + from_type: "Decimal".to_string(), + to_type: "f64".to_string(), + reason: format!("invalid Decimal value {}", unrealized_pnl), })? )?; position.volatility = market_data .volatility .map(|v| Price::from_f64(v) .map_err(|_| RiskError::TypeConversion { - from: "f64".to_string(), - to: "Price".to_string(), - value: v.to_string(), + from_type: "f64".to_string(), + to_type: "Price".to_string(), + reason: format!("invalid f64 value {}", v), }) ).transpose()?; position.last_updated = Utc::now(); diff --git a/risk/src/risk_engine.rs b/risk/src/risk_engine.rs index 671cb7d89..15d4013a4 100644 --- a/risk/src/risk_engine.rs +++ b/risk/src/risk_engine.rs @@ -19,6 +19,7 @@ use num::ToPrimitive; use uuid::Uuid; use std::marker::Send; use std::sync::Arc; +use tracing::error; // ELIMINATED: Prelude import removed to force explicit imports use rust_decimal::Decimal; use common::{Position, Symbol, Price, OrderSide, Quantity}; @@ -1701,8 +1702,7 @@ impl RiskEngine { .min( safe_divide( Decimal::try_from(self.config.position_limits.global_limit) - .map_err(|_| RiskError::ConfigurationError { - parameter: "global_limit".to_owned(), + .map_err(|_| RiskError::Configuration { message: "Invalid global limit configuration".to_owned(), })? .into(), diff --git a/risk/src/risk_types.rs b/risk/src/risk_types.rs index 1511f09f8..3c0fb79c3 100644 --- a/risk/src/risk_types.rs +++ b/risk/src/risk_types.rs @@ -11,6 +11,7 @@ use std::collections::HashMap; // ELIMINATED: Re-exports removed to force explicit imports use common::types::{Price, Quantity, Symbol, Volume, OrderType, OrderSide}; +use crate::error::RiskError; // Note: Side is an alias for OrderSide - using canonical OrderSide from trading_engine // Note: Side is an alias for OrderSide in common crate - both are available @@ -606,6 +607,10 @@ pub struct ComplianceConfig { pub position_limits: PositionLimits, /// Number of days to retain audit records for compliance pub audit_retention_days: u32, + /// Market abuse detection threshold + pub market_abuse_threshold: Option, + /// Large exposure threshold for regulatory reporting + pub large_exposure_threshold: Price, } /// Types of compliance warnings that can be issued diff --git a/risk/src/safety/emergency_response.rs b/risk/src/safety/emergency_response.rs index 63169327b..b8908b987 100644 --- a/risk/src/safety/emergency_response.rs +++ b/risk/src/safety/emergency_response.rs @@ -141,15 +141,15 @@ impl EmergencyResponseSystem { if metrics.max_drawdown.abs().to_decimal() .map_err(|e| RiskError::TypeConversion { - from: "Price".to_string(), - to: "Decimal".to_string(), - value: metrics.max_drawdown.to_string(), + from_type: "Price".to_string(), + to_type: "Decimal".to_string(), + reason: format!("conversion failed: {}", e), })? >= Decimal::try_from(0.20) .map_err(|e| RiskError::TypeConversion { - from: "f64".to_string(), - to: "Decimal".to_string(), - value: "0.20".to_string(), + from_type: "f64".to_string(), + to_type: "Decimal".to_string(), + reason: format!("conversion failed: {}", e), })? { // 20% drawdown limit