diff --git a/Cargo.lock b/Cargo.lock index 202b1a882..64905e542 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,16 +144,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - [[package]] name = "aes" version = "0.8.4" @@ -165,20 +155,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "ahash" version = "0.7.8" @@ -525,12 +501,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "arrayvec" version = "0.7.6" @@ -2021,19 +1991,6 @@ dependencies = [ "digest", ] -[[package]] -name = "blake3" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if 1.0.3", - "constant_time_eq 0.3.1", -] - [[package]] name = "block" version = "0.1.6" @@ -3305,7 +3262,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] @@ -3330,15 +3286,6 @@ dependencies = [ "memchr 2.7.5", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - [[package]] name = "cudarc" version = "0.12.1" @@ -5507,16 +5454,6 @@ dependencies = [ "wasi 0.14.7+wasi-0.2.4", ] -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - [[package]] name = "gimli" version = "0.31.1" @@ -7672,6 +7609,7 @@ dependencies = [ "dashmap", "fastrand 2.3.0", "flate2", + "foxhunt-config", "foxhunt-core", "fs2", "futures", @@ -8696,12 +8634,6 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl" version = "0.10.73" @@ -9758,18 +9690,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if 1.0.3", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - [[package]] name = "portable-atomic" version = "1.11.1" @@ -15040,16 +14960,11 @@ dependencies = [ name = "trading_service" version = "1.0.0" dependencies = [ - "aes-gcm", "anyhow", "async-stream", "async-trait", - "base64 0.22.1", - "blake3", - "chrono", "clap 4.5.48", "common", - "config", "data", "foxhunt-config", "foxhunt-core", @@ -15059,17 +14974,13 @@ dependencies = [ "ml", "once_cell", "prost 0.12.6", - "rand 0.8.5", "reqwest 0.12.4", "risk", "serde", "serde_json", - "sha2", - "sqlx", "storage", "tokio", "tokio-stream", - "toml", "tonic 0.12.3", "tonic-build", "tonic-health", @@ -15079,7 +14990,6 @@ dependencies = [ "tower-service", "tracing", "tracing-subscriber", - "vaultrs", ] [[package]] @@ -15321,16 +15231,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - [[package]] name = "unsafe-libyaml" version = "0.2.11" diff --git a/adaptive-strategy/src/lib.rs b/adaptive-strategy/src/lib.rs index 65ffb0f7f..fd48fe133 100644 --- a/adaptive-strategy/src/lib.rs +++ b/adaptive-strategy/src/lib.rs @@ -52,7 +52,7 @@ pub mod risk; use foxhunt_core::types::prelude::*; use anyhow::Result; -use foxhunt_config::StrategyConfig; +use foxhunt-config::StrategyConfig; use ensemble::EnsembleCoordinator; use serde::{Deserialize, Serialize}; use std::sync::Arc; diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 077f73b19..4e3fc3434 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -45,6 +45,9 @@ pub use manager::ConfigManager; pub use structures::*; pub use vault::{VaultConfig, VaultSecrets}; +// Re-export BacktestingConfig for convenience +pub use structures::BacktestingConfig; + // Re-export commonly used types pub use serde::{Deserialize, Serialize}; pub use serde_json::Value as JsonValue; diff --git a/crates/config/src/structures.rs b/crates/config/src/structures.rs index cdba34885..a0d4facb1 100644 --- a/crates/config/src/structures.rs +++ b/crates/config/src/structures.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; // Duration is used in default values +use std::time::Duration; /// Trading engine configuration #[derive(Debug, Clone, Serialize, Deserialize)] @@ -992,4 +993,315 @@ impl Default for KeyDerivationConfig { salt_size: 32, } } +} + +/// Backtesting service configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BacktestingConfig { + /// Server configuration + pub server: BacktestingServerConfig, + /// Database configuration + pub database: BacktestingDatabaseConfig, + /// Strategy engine configuration + pub strategy: BacktestingStrategyConfig, + /// Performance analysis configuration + pub performance: BacktestingPerformanceConfig, + /// Logging configuration + pub logging: BacktestingLoggingConfig, +} + +impl Default for BacktestingConfig { + fn default() -> Self { + Self { + server: BacktestingServerConfig::default(), + database: BacktestingDatabaseConfig::default(), + strategy: BacktestingStrategyConfig::default(), + performance: BacktestingPerformanceConfig::default(), + logging: BacktestingLoggingConfig::default(), + } + } +} + +/// Backtesting server configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BacktestingServerConfig { + /// Server bind address + pub address: String, + /// Maximum concurrent backtests + pub max_concurrent_backtests: usize, + /// Request timeout in seconds + pub request_timeout_secs: u64, + /// Enable TLS + pub enable_tls: bool, + /// TLS certificate path (if TLS enabled) + pub tls_cert_path: Option, + /// TLS private key path (if TLS enabled) + pub tls_key_path: Option, +} + +impl Default for BacktestingServerConfig { + fn default() -> Self { + Self { + address: "0.0.0.0:50053".to_string(), + max_concurrent_backtests: 10, + request_timeout_secs: 300, + enable_tls: false, + tls_cert_path: None, + tls_key_path: None, + } + } +} + +/// Backtesting database configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BacktestingDatabaseConfig { + /// PostgreSQL connection URL + pub postgres_url: String, + /// InfluxDB configuration + pub influxdb: BacktestingInfluxDbConfig, + /// Connection pool size + pub pool_size: u32, + /// Connection timeout in seconds + pub connection_timeout_secs: u64, + /// Query timeout in seconds + pub query_timeout_secs: u64, +} + +impl Default for BacktestingDatabaseConfig { + fn default() -> Self { + Self { + postgres_url: "postgresql://localhost:5432/foxhunt_backtesting".to_string(), + influxdb: BacktestingInfluxDbConfig::default(), + pool_size: 10, + connection_timeout_secs: 30, + query_timeout_secs: 60, + } + } +} + +/// Backtesting InfluxDB configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BacktestingInfluxDbConfig { + /// InfluxDB URL + pub url: String, + /// Database name + pub database: String, + /// Username (optional) + pub username: Option, + /// Password (optional) + pub password: Option, + /// Organization (for InfluxDB 2.x) + pub organization: Option, + /// Token (for InfluxDB 2.x) + pub token: Option, + /// Bucket (for InfluxDB 2.x) + pub bucket: Option, +} + +impl Default for BacktestingInfluxDbConfig { + fn default() -> Self { + Self { + url: "http://localhost:8086".to_string(), + database: "foxhunt_backtesting".to_string(), + username: None, + password: None, + organization: None, + token: None, + bucket: None, + } + } +} + +/// Backtesting strategy configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BacktestingStrategyConfig { + /// Default initial capital for backtests + pub default_initial_capital: f64, + /// Maximum backtest duration in days + pub max_backtest_duration_days: u32, + /// Data frequency for backtesting (e.g., "1m", "5m", "1h", "1d") + pub default_data_frequency: String, + /// Enable parallel execution + pub enable_parallel_execution: bool, + /// Number of worker threads for parallel execution + pub worker_threads: usize, + /// Commission rate (per trade) + pub commission_rate: f64, + /// Slippage rate (percentage) + pub slippage_rate: f64, + /// Enable transaction costs + pub enable_transaction_costs: bool, +} + +impl Default for BacktestingStrategyConfig { + fn default() -> Self { + Self { + default_initial_capital: 100000.0, + max_backtest_duration_days: 365 * 5, // 5 years + default_data_frequency: "1d".to_string(), + enable_parallel_execution: true, + worker_threads: num_cpus::get(), + commission_rate: 0.001, // 0.1% + slippage_rate: 0.0005, // 0.05% + enable_transaction_costs: true, + } + } +} + +/// Backtesting performance analysis configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BacktestingPerformanceConfig { + /// Risk-free rate for Sharpe ratio calculation + pub risk_free_rate: f64, + /// Benchmark symbol for comparison (e.g., "SPY") + pub benchmark_symbol: Option, + /// Enable detailed trade analysis + pub enable_detailed_analysis: bool, + /// Generate equity curve points + pub generate_equity_curve: bool, + /// Equity curve resolution (number of points) + pub equity_curve_resolution: usize, + /// Calculate rolling metrics + pub calculate_rolling_metrics: bool, + /// Rolling window size in days + pub rolling_window_days: u32, +} + +impl Default for BacktestingPerformanceConfig { + fn default() -> Self { + Self { + risk_free_rate: 0.02, // 2% annual + benchmark_symbol: Some("SPY".to_string()), + enable_detailed_analysis: true, + generate_equity_curve: true, + equity_curve_resolution: 1000, + calculate_rolling_metrics: true, + rolling_window_days: 30, + } + } +} + +/// Backtesting logging configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BacktestingLoggingConfig { + /// Log level + pub level: String, + /// Log format (json, pretty) + pub format: String, + /// Enable file logging + pub enable_file_logging: bool, + /// Log file path (if file logging enabled) + pub log_file_path: Option, + /// Log rotation size in MB + pub rotation_size_mb: u64, + /// Number of log files to keep + pub max_log_files: u32, +} + +impl Default for BacktestingLoggingConfig { + fn default() -> Self { + Self { + level: "info".to_string(), + format: "pretty".to_string(), + enable_file_logging: true, + log_file_path: Some("/var/log/foxhunt/backtesting_service.log".to_string()), + rotation_size_mb: 100, + max_log_files: 10, + } + } +} + +impl BacktestingConfig { + /// Load configuration from environment variables and config files + pub fn load() -> Result> { + let mut config = Self::default(); + + // Load from environment variables + if let Ok(address) = std::env::var("BACKTESTING_SERVER_ADDRESS") { + config.server.address = address; + } + + if let Ok(postgres_url) = std::env::var("BACKTESTING_POSTGRES_URL") { + config.database.postgres_url = postgres_url; + } + + if let Ok(influxdb_url) = std::env::var("BACKTESTING_INFLUXDB_URL") { + config.database.influxdb.url = influxdb_url; + } + + if let Ok(log_level) = std::env::var("BACKTESTING_LOG_LEVEL") { + config.logging.level = log_level; + } + + if let Ok(max_concurrent) = std::env::var("BACKTESTING_MAX_CONCURRENT") { + config.server.max_concurrent_backtests = max_concurrent.parse()?; + } + + if let Ok(initial_capital) = std::env::var("BACKTESTING_DEFAULT_CAPITAL") { + config.strategy.default_initial_capital = initial_capital.parse()?; + } + + if let Ok(commission_rate) = std::env::var("BACKTESTING_COMMISSION_RATE") { + config.strategy.commission_rate = commission_rate.parse()?; + } + + if let Ok(slippage_rate) = std::env::var("BACKTESTING_SLIPPAGE_RATE") { + config.strategy.slippage_rate = slippage_rate.parse()?; + } + + // Validate configuration + config.validate()?; + + Ok(config) + } + + /// Validate the configuration + pub fn validate(&self) -> Result<(), Box> { + // Validate server address + self.server.address.parse::()?; + + // Validate database URLs + if self.database.postgres_url.is_empty() { + return Err("PostgreSQL URL cannot be empty".into()); + } + + if self.database.influxdb.url.is_empty() { + return Err("InfluxDB URL cannot be empty".into()); + } + + // Validate strategy parameters + if self.strategy.default_initial_capital <= 0.0 { + return Err("Default initial capital must be positive".into()); + } + + if self.strategy.commission_rate < 0.0 || self.strategy.commission_rate > 1.0 { + return Err("Commission rate must be between 0 and 1".into()); + } + + if self.strategy.slippage_rate < 0.0 || self.strategy.slippage_rate > 1.0 { + return Err("Slippage rate must be between 0 and 1".into()); + } + + // Validate performance parameters + if self.performance.risk_free_rate < 0.0 || self.performance.risk_free_rate > 1.0 { + return Err("Risk-free rate must be between 0 and 1".into()); + } + + Ok(()) + } + + /// Get request timeout as Duration + pub fn request_timeout(&self) -> std::time::Duration { + std::time::Duration::from_secs(self.server.request_timeout_secs) + } + + /// Get connection timeout as Duration + pub fn connection_timeout(&self) -> std::time::Duration { + std::time::Duration::from_secs(self.database.connection_timeout_secs) + } + + /// Get query timeout as Duration + pub fn query_timeout(&self) -> std::time::Duration { + std::time::Duration::from_secs(self.database.query_timeout_secs) + } } \ No newline at end of file diff --git a/crates/config/src/vault.rs b/crates/config/src/vault.rs index d0d59d052..740a7652b 100644 --- a/crates/config/src/vault.rs +++ b/crates/config/src/vault.rs @@ -374,18 +374,18 @@ impl VaultSecrets { Ok(secret) => { // Cache the secret let mut cache = self.secrets_cache.write().await; - let secret_json = serde_json::to_value(&secret).unwrap_or_default(); - cache.insert(path.to_string(), (secret_json, Instant::now())); - + let secret_json: serde_json::Value = serde_json::to_value(&secret).unwrap_or(serde_json::Value::Null); + cache.insert(path.to_string(), (secret_json.clone(), Instant::now())); + self.handle_success().await; - + // Send notification if configured if let Some(ref tx) = self.notification_tx { - let _ = tx.send((path.to_string(), secret.clone())); + let _ = tx.send((path.to_string(), secret)); } debug!("Successfully retrieved secret from path: {}", path); - return Ok(Some(secret)); + return Ok(Some(secret_json)); } Err(e) => { last_error = Some(ConfigError::RetrievalError { diff --git a/ml/Cargo.toml b/ml/Cargo.toml index 6321f433b..721aa1261 100644 --- a/ml/Cargo.toml +++ b/ml/Cargo.toml @@ -45,6 +45,7 @@ optimization = ["argmin", "nlopt"] [dependencies] # Core Rust ecosystem foxhunt-core = { workspace = true } # Fixed namespace conflict with std::core +foxhunt-config = { workspace = true } # Configuration management # REMOVED: risk = { workspace = true } # CIRCULAR DEPENDENCY FIX - ML should not depend on risk tokio.workspace = true memmap2.workspace = true diff --git a/ml/src/features.rs b/ml/src/features.rs index ed51f045d..c088f833b 100644 --- a/ml/src/features.rs +++ b/ml/src/features.rs @@ -1855,7 +1855,7 @@ impl UnifiedFeatureExtractor { } } - Some((trend_score / (recent_data.len() - 1) as f64).abs()) + Some((trend_score as f64 / (recent_data.len() - 1) as f64).abs()) } async fn calculate_trend_consistency(&self, data: &[MarketData], window: usize) -> Option { @@ -2237,8 +2237,8 @@ impl UnifiedFeatureExtractor { let prev = market_data[market_data.len() - 2].price.to_f64(); if prev > 0.0 { - let change_ratio = (current / prev - 1.0).clamp(-0.05, 0.05); // 5% max - (0.5 + change_ratio * 10.0).clamp(0.2, 0.8) // Reduced range for uncertainty + let change_ratio = (current / prev - 1.0_f64).clamp(-0.05_f64, 0.05_f64); // 5% max + (0.5_f64 + change_ratio * 10.0_f64).clamp(0.2_f64, 0.8_f64) // Reduced range for uncertainty } else { 0.5 // Only when data is insufficient // Neutral when previous price is invalid } @@ -2322,7 +2322,7 @@ impl UnifiedFeatureExtractor { let volatility = variance.sqrt() / mean_price.max(1.0); // Higher volatility reduces signal confidence - (1.0 - (volatility * 20.0).min(0.4)).max(0.6) + (1.0_f64 - (volatility * 20.0_f64).min(0.4_f64)).max(0.6_f64) } /// Detect market regime for signal adjustment @@ -2441,7 +2441,7 @@ impl UnifiedFeatureExtractor { if mean > 0.0 { let cv = variance.sqrt() / mean; // Coefficient of variation - (1.0 - cv.min(1.0)).clamp(0.1, 0.9) + (1.0_f64 - cv.min(1.0_f64)).clamp(0.1_f64, 0.9_f64) } else { 0.5 } @@ -2581,7 +2581,7 @@ impl UnifiedFeatureExtractor { 1.0 }; - (1.0 - cv).clamp(0.1, 0.95) + (1.0_f64 - cv).clamp(0.1_f64, 0.95_f64) } /// Classify trade sign: -1 (sell), 0 (neutral), +1 (buy) @@ -2863,7 +2863,7 @@ impl UnifiedFeatureExtractor { // Annualize assuming this is daily data let days = data.len() as f64; - let annualized_return = (1.0 + total_return).powf(252.0 / days) - 1.0; + let annualized_return = (1.0_f64 + total_return).powf(252.0_f64 / days) - 1.0_f64; // Calculate max drawdown let max_dd = self diff --git a/ml/src/mamba/mod.rs b/ml/src/mamba/mod.rs index 70d245157..bd4e64c2a 100644 --- a/ml/src/mamba/mod.rs +++ b/ml/src/mamba/mod.rs @@ -480,7 +480,7 @@ impl Mamba2SSM { } /// Forward pass through SSD layer with selective scan - #[instrument(skip(self, ssd_layer, input))] + #[instrument(skip(self, _ssd_layer, input))] fn forward_ssd_layer( &mut self, _ssd_layer: &SSDLayer, diff --git a/ml/src/mamba/selective_state.rs b/ml/src/mamba/selective_state.rs index 9456666c3..bc1a27441 100644 --- a/ml/src/mamba/selective_state.rs +++ b/ml/src/mamba/selective_state.rs @@ -272,7 +272,7 @@ impl SelectiveStateSpace { } /// Update importance scores based on input - #[instrument(skip(self, input, state))] + #[instrument(skip(self, input, _state))] pub fn update_importance_scores( &mut self, input: &Tensor, diff --git a/risk/src/lib.rs b/risk/src/lib.rs index 3c7e602e8..20e8b4191 100644 --- a/risk/src/lib.rs +++ b/risk/src/lib.rs @@ -147,7 +147,7 @@ pub use safety::{ // Circuit breakers and monitoring pub use circuit_breaker::{CircuitBreakerConfig, CircuitBreakerState}; -pub use foxhunt_config::RiskConfig; +pub use foxhunt-config::RiskConfig; pub use drawdown_monitor::DrawdownMonitor; // Removed missing type: CircuitBreaker // Removed missing type: ComplianceMonitor diff --git a/services/backtesting_service/src/config.rs b/services/backtesting_service/src/config.rs deleted file mode 100644 index a3d303476..000000000 --- a/services/backtesting_service/src/config.rs +++ /dev/null @@ -1,351 +0,0 @@ -//! Configuration management for the backtesting service - -use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; -use std::time::Duration; - -/// Main configuration structure for the backtesting service -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BacktestingConfig { - /// Server configuration - pub server: ServerConfig, - /// Database configuration - pub database: DatabaseConfig, - /// Strategy engine configuration - pub strategy: StrategyConfig, - /// Performance analysis configuration - pub performance: PerformanceConfig, - /// Logging configuration - pub logging: LoggingConfig, -} - -/// Server configuration -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ServerConfig { - /// Server bind address - pub address: String, - /// Maximum concurrent backtests - pub max_concurrent_backtests: usize, - /// Request timeout in seconds - pub request_timeout_secs: u64, - /// Enable TLS - pub enable_tls: bool, - /// TLS certificate path (if TLS enabled) - pub tls_cert_path: Option, - /// TLS private key path (if TLS enabled) - pub tls_key_path: Option, -} - -/// Database configuration -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DatabaseConfig { - /// PostgreSQL connection URL - pub postgres_url: String, - /// InfluxDB configuration - pub influxdb: InfluxDbConfig, - /// Connection pool size - pub pool_size: u32, - /// Connection timeout in seconds - pub connection_timeout_secs: u64, - /// Query timeout in seconds - pub query_timeout_secs: u64, -} - -/// InfluxDB configuration -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct InfluxDbConfig { - /// InfluxDB URL - pub url: String, - /// Database name - pub database: String, - /// Username (optional) - pub username: Option, - /// Password (optional) - pub password: Option, - /// Organization (for InfluxDB 2.x) - pub organization: Option, - /// Token (for InfluxDB 2.x) - pub token: Option, - /// Bucket (for InfluxDB 2.x) - pub bucket: Option, -} - -/// Strategy engine configuration -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct StrategyConfig { - /// Default initial capital for backtests - pub default_initial_capital: f64, - /// Maximum backtest duration in days - pub max_backtest_duration_days: u32, - /// Data frequency for backtesting (e.g., "1m", "5m", "1h", "1d") - pub default_data_frequency: String, - /// Enable parallel execution - pub enable_parallel_execution: bool, - /// Number of worker threads for parallel execution - pub worker_threads: usize, - /// Commission rate (per trade) - pub commission_rate: f64, - /// Slippage rate (percentage) - pub slippage_rate: f64, - /// Enable transaction costs - pub enable_transaction_costs: bool, -} - -/// Performance analysis configuration -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PerformanceConfig { - /// Risk-free rate for Sharpe ratio calculation - pub risk_free_rate: f64, - /// Benchmark symbol for comparison (e.g., "SPY") - pub benchmark_symbol: Option, - /// Enable detailed trade analysis - pub enable_detailed_analysis: bool, - /// Generate equity curve points - pub generate_equity_curve: bool, - /// Equity curve resolution (number of points) - pub equity_curve_resolution: usize, - /// Calculate rolling metrics - pub calculate_rolling_metrics: bool, - /// Rolling window size in days - pub rolling_window_days: u32, -} - -/// Logging configuration -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LoggingConfig { - /// Log level - pub level: String, - /// Log format (json, pretty) - pub format: String, - /// Enable file logging - pub enable_file_logging: bool, - /// Log file path (if file logging enabled) - pub log_file_path: Option, - /// Log rotation size in MB - pub rotation_size_mb: u64, - /// Number of log files to keep - pub max_log_files: u32, -} - -impl Default for BacktestingConfig { - fn default() -> Self { - Self { - server: ServerConfig { - address: "0.0.0.0:50053".to_string(), - max_concurrent_backtests: 10, - request_timeout_secs: 300, - enable_tls: false, - tls_cert_path: None, - tls_key_path: None, - }, - database: DatabaseConfig { - postgres_url: "postgresql://localhost:5432/foxhunt_backtesting".to_string(), - influxdb: InfluxDbConfig { - url: "http://localhost:8086".to_string(), - database: "foxhunt_backtesting".to_string(), - username: None, - password: None, - organization: None, - token: None, - bucket: None, - }, - pool_size: 10, - connection_timeout_secs: 30, - query_timeout_secs: 60, - }, - strategy: StrategyConfig { - default_initial_capital: 100000.0, - max_backtest_duration_days: 365 * 5, // 5 years - default_data_frequency: "1d".to_string(), - enable_parallel_execution: true, - worker_threads: num_cpus::get(), - commission_rate: 0.001, // 0.1% - slippage_rate: 0.0005, // 0.05% - enable_transaction_costs: true, - }, - performance: PerformanceConfig { - risk_free_rate: 0.02, // 2% annual - benchmark_symbol: Some("SPY".to_string()), - enable_detailed_analysis: true, - generate_equity_curve: true, - equity_curve_resolution: 1000, - calculate_rolling_metrics: true, - rolling_window_days: 30, - }, - logging: LoggingConfig { - level: "info".to_string(), - format: "pretty".to_string(), - enable_file_logging: true, - log_file_path: Some("/var/log/foxhunt/backtesting_service.log".to_string()), - rotation_size_mb: 100, - max_log_files: 10, - }, - } - } -} - -impl BacktestingConfig { - /// Load configuration from environment variables and config files - pub fn load() -> Result { - // Start with default configuration - let mut config = Self::default(); - - // Load from environment variables - dotenvy::dotenv().ok(); // Ignore if .env file doesn't exist - - // Override with environment variables - if let Ok(address) = std::env::var("BACKTESTING_SERVER_ADDRESS") { - config.server.address = address; - } - - if let Ok(postgres_url) = std::env::var("BACKTESTING_POSTGRES_URL") { - config.database.postgres_url = postgres_url; - } - - if let Ok(influxdb_url) = std::env::var("BACKTESTING_INFLUXDB_URL") { - config.database.influxdb.url = influxdb_url; - } - - if let Ok(influxdb_database) = std::env::var("BACKTESTING_INFLUXDB_DATABASE") { - config.database.influxdb.database = influxdb_database; - } - - if let Ok(influxdb_username) = std::env::var("BACKTESTING_INFLUXDB_USERNAME") { - config.database.influxdb.username = Some(influxdb_username); - } - - if let Ok(influxdb_password) = std::env::var("BACKTESTING_INFLUXDB_PASSWORD") { - config.database.influxdb.password = Some(influxdb_password); - } - - if let Ok(influxdb_token) = std::env::var("BACKTESTING_INFLUXDB_TOKEN") { - config.database.influxdb.token = Some(influxdb_token); - } - - if let Ok(influxdb_org) = std::env::var("BACKTESTING_INFLUXDB_ORG") { - config.database.influxdb.organization = Some(influxdb_org); - } - - if let Ok(influxdb_bucket) = std::env::var("BACKTESTING_INFLUXDB_BUCKET") { - config.database.influxdb.bucket = Some(influxdb_bucket); - } - - if let Ok(log_level) = std::env::var("BACKTESTING_LOG_LEVEL") { - config.logging.level = log_level; - } - - if let Ok(max_concurrent) = std::env::var("BACKTESTING_MAX_CONCURRENT") { - config.server.max_concurrent_backtests = max_concurrent - .parse() - .context("Invalid BACKTESTING_MAX_CONCURRENT value")?; - } - - if let Ok(initial_capital) = std::env::var("BACKTESTING_DEFAULT_CAPITAL") { - config.strategy.default_initial_capital = initial_capital - .parse() - .context("Invalid BACKTESTING_DEFAULT_CAPITAL value")?; - } - - if let Ok(commission_rate) = std::env::var("BACKTESTING_COMMISSION_RATE") { - config.strategy.commission_rate = commission_rate - .parse() - .context("Invalid BACKTESTING_COMMISSION_RATE value")?; - } - - if let Ok(slippage_rate) = std::env::var("BACKTESTING_SLIPPAGE_RATE") { - config.strategy.slippage_rate = slippage_rate - .parse() - .context("Invalid BACKTESTING_SLIPPAGE_RATE value")?; - } - - // Validate configuration - config.validate()?; - - Ok(config) - } - - /// Validate the configuration - pub fn validate(&self) -> Result<()> { - // Validate server address - self.server - .address - .parse::() - .context("Invalid server address")?; - - // Validate database URLs - if self.database.postgres_url.is_empty() { - anyhow::bail!("PostgreSQL URL cannot be empty"); - } - - if self.database.influxdb.url.is_empty() { - anyhow::bail!("InfluxDB URL cannot be empty"); - } - - // Validate strategy parameters - if self.strategy.default_initial_capital <= 0.0 { - anyhow::bail!("Default initial capital must be positive"); - } - - if self.strategy.commission_rate < 0.0 || self.strategy.commission_rate > 1.0 { - anyhow::bail!("Commission rate must be between 0 and 1"); - } - - if self.strategy.slippage_rate < 0.0 || self.strategy.slippage_rate > 1.0 { - anyhow::bail!("Slippage rate must be between 0 and 1"); - } - - // Validate performance parameters - if self.performance.risk_free_rate < 0.0 || self.performance.risk_free_rate > 1.0 { - anyhow::bail!("Risk-free rate must be between 0 and 1"); - } - - Ok(()) - } - - /// Get request timeout as Duration - pub fn request_timeout(&self) -> Duration { - Duration::from_secs(self.server.request_timeout_secs) - } - - /// Get connection timeout as Duration - pub fn connection_timeout(&self) -> Duration { - Duration::from_secs(self.database.connection_timeout_secs) - } - - /// Get query timeout as Duration - pub fn query_timeout(&self) -> Duration { - Duration::from_secs(self.database.query_timeout_secs) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_default_config_validation() { - let config = BacktestingConfig::default(); - assert!(config.validate().is_ok()); - } - - #[test] - fn test_invalid_commission_rate() { - let mut config = BacktestingConfig::default(); - config.strategy.commission_rate = 1.5; // Invalid: > 1.0 - assert!(config.validate().is_err()); - } - - #[test] - fn test_invalid_slippage_rate() { - let mut config = BacktestingConfig::default(); - config.strategy.slippage_rate = -0.1; // Invalid: < 0.0 - assert!(config.validate().is_err()); - } - - #[test] - fn test_invalid_initial_capital() { - let mut config = BacktestingConfig::default(); - config.strategy.default_initial_capital = -1000.0; // Invalid: <= 0 - assert!(config.validate().is_err()); - } -} diff --git a/services/backtesting_service/src/main.rs b/services/backtesting_service/src/main.rs index 4d05c07c6..0a3d6a0a6 100644 --- a/services/backtesting_service/src/main.rs +++ b/services/backtesting_service/src/main.rs @@ -13,7 +13,6 @@ use tonic::transport::Server; use tracing::{error, info, warn}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; -mod config; mod performance; mod service; mod storage; @@ -26,7 +25,7 @@ mod foxhunt { } } -use foxhunt_config::BacktestingConfig; +use foxhunt-config::BacktestingConfig; use service::BacktestingServiceImpl; /// Main entry point for the backtesting service diff --git a/services/backtesting_service/src/ml_strategy_engine.rs b/services/backtesting_service/src/ml_strategy_engine.rs index 9eb4793f1..71ca0bf00 100644 --- a/services/backtesting_service/src/ml_strategy_engine.rs +++ b/services/backtesting_service/src/ml_strategy_engine.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use foxhunt_core::types::prelude::*; -use crate::config::StrategyConfig; +use foxhunt_config::BacktestingStrategyConfig; use crate::storage::StorageManager; use crate::strategy_engine::{MarketData, BacktestTrade, TradeSide, TradeSignal, StrategyExecutor, Portfolio}; @@ -529,7 +529,7 @@ pub struct MLStrategyEngine { impl MLStrategyEngine { /// Create new ML strategy engine pub async fn new( - config: &StrategyConfig, + config: &BacktestingStrategyConfig, storage_manager: Arc, ) -> Result { let base_engine = crate::strategy_engine::StrategyEngine::new(config, storage_manager).await?; diff --git a/services/backtesting_service/src/performance.rs b/services/backtesting_service/src/performance.rs index 9e4f32e0d..620087b9a 100644 --- a/services/backtesting_service/src/performance.rs +++ b/services/backtesting_service/src/performance.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use tracing::{debug, info}; -use crate::config::PerformanceConfig; +use foxhunt_config::BacktestingPerformanceConfig; use crate::strategy_engine::BacktestTrade; /// Comprehensive performance metrics @@ -103,12 +103,12 @@ pub struct RollingMetrics { #[derive(Debug)] pub struct PerformanceAnalyzer { /// Configuration - config: PerformanceConfig, + config: BacktestingPerformanceConfig, } impl PerformanceAnalyzer { /// Create a new performance analyzer - pub fn new(config: &PerformanceConfig) -> Result { + pub fn new(config: &BacktestingPerformanceConfig) -> Result { info!("Initializing performance analyzer"); Ok(Self { config: config.clone(), diff --git a/services/backtesting_service/src/service.rs b/services/backtesting_service/src/service.rs index 877ef2712..176a0ffef 100644 --- a/services/backtesting_service/src/service.rs +++ b/services/backtesting_service/src/service.rs @@ -8,7 +8,7 @@ use tonic::{Request, Response, Status}; use tracing::{debug, error, info, warn}; use uuid::Uuid; -use crate::config::BacktestingConfig; +use foxhunt_config::BacktestingConfig; use crate::foxhunt::tli::{backtesting_service_server::BacktestingService, *}; use crate::performance::PerformanceAnalyzer; use crate::storage::StorageManager; diff --git a/services/backtesting_service/src/storage.rs b/services/backtesting_service/src/storage.rs index ca5ec185f..94d1e7674 100644 --- a/services/backtesting_service/src/storage.rs +++ b/services/backtesting_service/src/storage.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use tracing::{debug, error, info}; use uuid::Uuid; -use crate::config::DatabaseConfig; +use foxhunt_config::BacktestingDatabaseConfig; use crate::foxhunt::tli::BacktestStatus; use crate::performance::PerformanceMetrics; use crate::strategy_engine::BacktestTrade; @@ -50,7 +50,7 @@ pub struct StorageManager { impl StorageManager { /// Create a new storage manager - pub async fn new(config: &DatabaseConfig) -> Result { + pub async fn new(config: &BacktestingDatabaseConfig) -> Result { info!("Initializing storage manager"); // Connect to PostgreSQL diff --git a/services/backtesting_service/src/strategy_engine.rs b/services/backtesting_service/src/strategy_engine.rs index c451d7ca3..c659f1dd3 100644 --- a/services/backtesting_service/src/strategy_engine.rs +++ b/services/backtesting_service/src/strategy_engine.rs @@ -13,7 +13,7 @@ use data::unified_feature_extractor::{UnifiedFeatureExtractor, UnifiedFeatureExt use data::types::{MarketDataEvent, TradeEvent}; use foxhunt_core::types::prelude::*; -use crate::config::StrategyConfig; +use foxhunt_config::BacktestingStrategyConfig; use crate::storage::StorageManager; /// Market data structure for backtesting @@ -254,7 +254,7 @@ impl Portfolio { /// Strategy execution engine for backtesting pub struct StrategyEngine { /// Configuration - config: StrategyConfig, + config: BacktestingStrategyConfig, /// Storage manager storage_manager: Arc, /// Available strategies @@ -506,7 +506,7 @@ impl StrategyExecutor for BuyAndHoldStrategy { impl StrategyEngine { /// Create a new strategy engine pub async fn new( - config: &StrategyConfig, + config: &BacktestingStrategyConfig, storage_manager: Arc, ) -> Result { info!("Initializing strategy engine with dual-provider architecture"); diff --git a/services/ml_training_service/src/lib.rs b/services/ml_training_service/src/lib.rs index 4a5805b10..e02038540 100644 --- a/services/ml_training_service/src/lib.rs +++ b/services/ml_training_service/src/lib.rs @@ -16,7 +16,7 @@ pub mod storage; pub mod vault; // Re-export commonly used types -pub use foxhunt_config::ServiceConfig; +pub use foxhunt-config::ServiceConfig; pub use database::{DatabaseManager, TrainingJobRecord}; pub use orchestrator::{JobStatus, TrainingJob, TrainingOrchestrator}; pub use service::MLTrainingServiceImpl; diff --git a/services/ml_training_service/src/main.rs b/services/ml_training_service/src/main.rs index 3608bb76a..b0969b843 100644 --- a/services/ml_training_service/src/main.rs +++ b/services/ml_training_service/src/main.rs @@ -24,7 +24,7 @@ mod service; mod storage; mod vault; -use foxhunt_config::ServiceConfig; +use foxhunt-config::ServiceConfig; use database::DatabaseManager; use encryption::EncryptionKeyManager; use gpu_config::GpuConfigManager; diff --git a/services/trading_service/Cargo.toml b/services/trading_service/Cargo.toml index 721066ffc..35d9033ba 100644 --- a/services/trading_service/Cargo.toml +++ b/services/trading_service/Cargo.toml @@ -33,36 +33,16 @@ tower.workspace = true tower-layer = "0.3" tower-service = "0.3" -# Database -sqlx.workspace = true - # Async utilities tokio-stream.workspace = true async-stream = "0.3" futures.workspace = true async-trait.workspace = true -# Configuration -config.workspace = true -toml.workspace = true - -# Security -sha2.workspace = true -blake3 = "1.5" -aes-gcm = "0.10" -rand.workspace = true -base64.workspace = true - # Networking hyper.workspace = true reqwest.workspace = true -# Time handling -chrono.workspace = true - -# HashiCorp Vault integration -vaultrs = { version = "0.7", features = ["rustls"] } - # Performance metrics hdrhistogram = "7.5" once_cell.workspace = true @@ -73,9 +53,11 @@ foxhunt-core = { path = "../../core" } risk = { path = "../../risk" } ml = { path = "../../ml" } data = { path = "../../data" } -common = { path = "../../common" } -storage = { path = "../../storage" } -foxhunt-config = { path = "../../crates/config" } + +# Shared libraries - primary dependencies +common = { path = "../../common", features = ["database"] } +storage = { path = "../../storage", features = ["s3", "vault-integration"] } +foxhunt-config = { path = "../../crates/config", features = ["postgres", "vault"] } # Build dependencies [build-dependencies] diff --git a/services/trading_service/src/error.rs b/services/trading_service/src/error.rs index ca9e82c82..dd6104ec4 100644 --- a/services/trading_service/src/error.rs +++ b/services/trading_service/src/error.rs @@ -1,79 +1,63 @@ -//! Error types for the Trading Service +//! Error types for the Trading Service - Using Shared Library Types -use thiserror::Error; +// Re-export shared error types and utilities +pub use common::error::{CommonError, CommonResult, ErrorCategory, RetryStrategy}; +pub use common::prelude::*; -/// Main error type for trading service operations -#[derive(Debug, Error)] +/// Trading service specific error extensions +/// For cases where we need domain-specific error information +#[derive(Debug, thiserror::Error)] pub enum TradingServiceError { - /// Database operation failed - #[error("Database error: {0}")] - Database(#[from] sqlx::Error), + /// Shared library error with context + #[error("Trading service error: {0}")] + Common(#[from] CommonError), - /// gRPC/tonic error - #[error("gRPC error: {0}")] - Grpc(#[from] tonic::Status), - - /// Configuration error - #[error("Configuration error: {message}")] - Configuration { message: String }, - - /// Order validation failed + /// Order validation failed with specific trading context #[error("Order validation failed: {reason}")] OrderValidation { reason: String }, - /// Risk management violation + /// Risk management violation with trading-specific details #[error("Risk violation: {violation_type} - {message}")] RiskViolation { violation_type: String, message: String, }, - /// ML model error + /// ML model error with model context #[error("ML model error: {model_name} - {message}")] MLModel { model_name: String, message: String }, - - /// Market data error - #[error("Market data error: {source} - {message}")] - MarketData { source: String, message: String }, - - /// Broker connectivity error - #[error("Broker error: {broker} - {message}")] - Broker { broker: String, message: String }, - - /// Internal system error - #[error("Internal error: {message}")] - Internal { message: String }, - - /// Serialization/deserialization error - #[error("Serialization error: {0}")] - Serialization(#[from] serde_json::Error), - - /// Network/IO error - #[error("IO error: {0}")] - Io(#[from] std::io::Error), - - /// Authentication/authorization error - #[error("Auth error: {message}")] - Auth { message: String }, - - /// Resource not found - #[error("Not found: {resource} with id {id}")] - NotFound { resource: String, id: String }, - - /// Service unavailable - #[error("Service unavailable: {service} - {reason}")] - ServiceUnavailable { service: String, reason: String }, } -/// Result type for trading service operations +/// Result type for trading service operations pub type TradingServiceResult = Result; +/// Convenience type alias using common result +pub type Result = CommonResult; + /// Convert TradingServiceError to tonic::Status for gRPC responses impl From for tonic::Status { fn from(err: TradingServiceError) -> Self { match err { - TradingServiceError::NotFound { resource, id } => { - tonic::Status::not_found(format!("{} with id {} not found", resource, id)) + TradingServiceError::Common(common_err) => { + // Leverage shared error to gRPC status conversion + match common_err { + CommonError::NotFound { resource, .. } => { + tonic::Status::not_found(format!("{} not found", resource)) + } + CommonError::Authentication { .. } => { + tonic::Status::unauthenticated(common_err.to_string()) + } + CommonError::Authorization { .. } => { + tonic::Status::permission_denied(common_err.to_string()) + } + CommonError::Validation { .. } => { + tonic::Status::invalid_argument(common_err.to_string()) + } + CommonError::ServiceUnavailable { .. } => { + tonic::Status::unavailable(common_err.to_string()) + } + _ => tonic::Status::internal(common_err.to_string()), + } } TradingServiceError::OrderValidation { reason } => { tonic::Status::invalid_argument(format!("Order validation failed: {}", reason)) @@ -85,14 +69,9 @@ impl From for tonic::Status { "Risk violation {}: {}", violation_type, message )), - TradingServiceError::Auth { message } => tonic::Status::unauthenticated(message), - TradingServiceError::ServiceUnavailable { service, reason } => { - tonic::Status::unavailable(format!("Service {} unavailable: {}", service, reason)) + TradingServiceError::MLModel { model_name, message } => { + tonic::Status::internal(format!("ML model {} error: {}", model_name, message)) } - TradingServiceError::Configuration { message } => { - tonic::Status::invalid_argument(format!("Configuration error: {}", message)) - } - _ => tonic::Status::internal(err.to_string()), } } } diff --git a/services/trading_service/src/lib.rs b/services/trading_service/src/lib.rs index 67e9b61eb..17c53f865 100644 --- a/services/trading_service/src/lib.rs +++ b/services/trading_service/src/lib.rs @@ -83,6 +83,12 @@ pub mod utils; /// Re-exports for convenient access pub mod prelude { + // Re-export shared library functionality + pub use common::prelude::*; + pub use foxhunt_config::*; + pub use storage::*; + + // Re-export trading service specific modules pub use crate::config::*; pub use crate::error::*; pub use crate::event_streaming::*; diff --git a/services/trading_service/src/main.rs b/services/trading_service/src/main.rs index a4b736730..3f536ac6c 100644 --- a/services/trading_service/src/main.rs +++ b/services/trading_service/src/main.rs @@ -15,7 +15,11 @@ use tracing::{error, info, warn}; use trading_service::auth_interceptor::{AuthConfig, AuthLayer}; use trading_service::tls_config::{TradingServiceTlsConfig, TlsInterceptor, VaultTlsConfig}; -use foxhunt_config::{ConfigManager, ConfigCategory}; +// Use shared libraries for configuration and common functionality +use common::prelude::*; +use foxhunt-config::{ConfigManager, ConfigCategory}; +use storage::prelude::*; + use trading_service::kill_switch_integration::TradingServiceKillSwitch; use trading_service::prelude::*; use trading_service::services::{EnhancedMLServiceImpl, MLFallbackManager, MLPerformanceMonitor}; @@ -38,9 +42,13 @@ async fn main() -> Result<()> { let config = load_service_config().await?; info!("Service configuration loaded"); - // Initialize centralized ConfigManager + // Initialize centralized ConfigManager using shared library + let db_config = common::database::DatabaseConfig::from_url(&config.postgres_url)? + .with_pool_size(10) + .with_timeout(std::time::Duration::from_secs(30)); + let config_manager = Arc::new( - ConfigManager::new(&config.postgres_url) + ConfigManager::new(db_config, None) .await .context("Failed to initialize ConfigManager")?, ); @@ -287,10 +295,10 @@ async fn initialize_default_configs(config_manager: &ConfigManager) -> Result<() } // ML Model Settings - if config_manager.get_config::(ConfigCategory::ML, "inference_timeout_ms").await?.is_none() { + if config_manager.get_config::(ConfigCategory::MachineLearning, "inference_timeout_ms").await?.is_none() { config_manager .set_config( - ConfigCategory::ML, + ConfigCategory::MachineLearning, "inference_timeout_ms", &100u64, // 100ms timeout ) @@ -298,10 +306,10 @@ async fn initialize_default_configs(config_manager: &ConfigManager) -> Result<() } // Broker Connections - if config_manager.get_config::(ConfigCategory::Broker, "connection_timeout_ms").await?.is_none() { + if config_manager.get_config::(ConfigCategory::Brokers, "connection_timeout_ms").await?.is_none() { config_manager .set_config( - ConfigCategory::Broker, + ConfigCategory::Brokers, "connection_timeout_ms", &5000u64, // 5 second timeout ) @@ -329,6 +337,11 @@ async fn start_config_monitoring(config_manager: Arc) -> Result<( info!("Updated max order size to: ${}", value); } } + (ConfigCategory::MachineLearning, "inference_timeout_ms") => { + if let Ok(Some(value)) = config_manager.get_config::(ConfigCategory::MachineLearning, "inference_timeout_ms").await { + info!("Updated ML inference timeout to: {}ms", value); + } + } (ConfigCategory::Risk, "var_confidence") => { if let Ok(Some(value)) = config_manager.get_config::(ConfigCategory::Risk, "var_confidence").await { info!("Updated VaR confidence to: {}", value); diff --git a/services/trading_service/src/services/mod.rs b/services/trading_service/src/services/mod.rs index fbd514ffa..6f40fc9b2 100644 --- a/services/trading_service/src/services/mod.rs +++ b/services/trading_service/src/services/mod.rs @@ -11,7 +11,7 @@ pub mod trading; pub mod ml_fallback_manager; pub mod ml_performance_monitor; -pub use foxhunt_config::ConfigServiceImpl; +pub use foxhunt-config::ConfigServiceImpl; pub use enhanced_ml::EnhancedMLServiceImpl; pub use ml::MLServiceImpl; pub use ml_fallback_manager::MLFallbackManager; diff --git a/services/trading_service/src/state.rs b/services/trading_service/src/state.rs index 43d8ebe40..dd624eb87 100644 --- a/services/trading_service/src/state.rs +++ b/services/trading_service/src/state.rs @@ -4,7 +4,7 @@ extern crate foxhunt_core; extern crate data; extern crate ml; -use foxhunt_config::ConfigManager; +use foxhunt-config::ConfigManager; use crate::error::TradingServiceResult; use foxhunt_core::prelude::*; use sqlx::SqlitePool; @@ -164,7 +164,7 @@ impl RiskEngine { pub async fn initialize_with_config(&mut self, config_manager: &ConfigManager) -> TradingServiceResult<()> { // Initialize risk parameters from centralized configuration - use foxhunt_config::ConfigCategory; + use foxhunt-config::ConfigCategory; // Load VaR confidence from config if let Ok(Some(var_confidence)) = config_manager.get_config::(ConfigCategory::Risk, "var_confidence").await { diff --git a/services/trading_service/src/utils.rs b/services/trading_service/src/utils.rs index 944d05006..6155a7d69 100644 --- a/services/trading_service/src/utils.rs +++ b/services/trading_service/src/utils.rs @@ -1,30 +1,27 @@ //! # Trading Service Utilities Module //! -//! Common utilities for the trading service including order validation, risk calculations, -//! performance monitoring, and helper functions for high-frequency trading operations. +//! Trading-specific utilities that extend shared library functionality. +//! Focus on domain-specific trading operations that aren't available in common libraries. //! //! ## Features //! -//! - Order validation and sanitization -//! - Risk metric calculations and position management -//! - Performance monitoring for trading operations -//! - Trading-specific data structures and helpers -//! - Portfolio calculations and P&L tracking +//! - Order validation specific to trading rules +//! - Trading-specific helper functions +//! - Domain-specific calculations +// Use shared library functionality +use common::prelude::*; use crate::error::{Result, TradingServiceError}; -use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::Arc; -use std::time::{Duration, Instant}; use tracing::{debug, info, warn}; -/// Order validation utilities +/// Trading-specific order validation utilities pub mod validation { use super::*; /// Order validator for trading operations + #[derive(Debug, Clone)] pub struct OrderValidator { max_order_size: f64, min_order_size: f64, @@ -50,19 +47,17 @@ pub mod validation { } } - /// Validate order size + /// Validate order size within trading limits pub fn validate_order_size(&self, size: f64) -> Result<()> { if size <= 0.0 { - return Err(TradingServiceError::ValidationError { - field: "order_size".to_string(), - message: "Order size must be positive".to_string(), + return Err(TradingServiceError::OrderValidation { + reason: "Order size must be positive".to_string(), }); } if size < self.min_order_size { - return Err(TradingServiceError::ValidationError { - field: "order_size".to_string(), - message: format!( + return Err(TradingServiceError::OrderValidation { + reason: format!( "Order size {:.6} below minimum {:.6}", size, self.min_order_size ), @@ -70,9 +65,8 @@ pub mod validation { } if size > self.max_order_size { - return Err(TradingServiceError::ValidationError { - field: "order_size".to_string(), - message: format!( + return Err(TradingServiceError::OrderValidation { + reason: format!( "Order size {:.6} exceeds maximum {:.6}", size, self.max_order_size ), @@ -85,17 +79,15 @@ pub mod validation { /// Validate order price against market data pub fn validate_price(&self, price: f64, market_price: f64) -> Result<()> { if price <= 0.0 { - return Err(TradingServiceError::ValidationError { - field: "price".to_string(), - message: "Price must be positive".to_string(), + return Err(TradingServiceError::OrderValidation { + reason: "Price must be positive".to_string(), }); } let deviation = ((price - market_price) / market_price).abs() * 100.0; if deviation > self.max_price_deviation { - return Err(TradingServiceError::ValidationError { - field: "price_deviation".to_string(), - message: format!( + return Err(TradingServiceError::OrderValidation { + reason: format!( "Price deviation {:.2}% exceeds maximum {:.2}%", deviation, self.max_price_deviation ), @@ -108,18 +100,16 @@ pub mod validation { /// Validate trading symbol pub fn validate_symbol(&self, symbol: &str) -> Result<()> { if symbol.is_empty() { - return Err(TradingServiceError::ValidationError { - field: "symbol".to_string(), - message: "Symbol cannot be empty".to_string(), + return Err(TradingServiceError::OrderValidation { + reason: "Symbol cannot be empty".to_string(), }); } if self.enable_symbol_validation { if let Some(ref allowed) = self.allowed_symbols { if !allowed.contains(&symbol.to_string()) { - return Err(TradingServiceError::ValidationError { - field: "symbol".to_string(), - message: format!("Symbol '{}' not in allowed list", symbol), + return Err(TradingServiceError::OrderValidation { + reason: format!("Symbol '{}' not in allowed list", symbol), }); } } @@ -133,9 +123,8 @@ pub mod validation { match order_type { "MARKET" => { if time_in_force != "IOC" && time_in_force != "FOK" { - return Err(TradingServiceError::ValidationError { - field: "time_in_force".to_string(), - message: "Market orders must use IOC or FOK".to_string(), + return Err(TradingServiceError::OrderValidation { + reason: "Market orders must use IOC or FOK".to_string(), }); } } @@ -143,9 +132,8 @@ pub mod validation { // Limit orders can use any TIF } _ => { - return Err(TradingServiceError::ValidationError { - field: "order_type".to_string(), - message: format!("Invalid order type: {}", order_type), + return Err(TradingServiceError::OrderValidation { + reason: format!("Invalid order type: {}", order_type), }); } } @@ -167,34 +155,32 @@ pub mod validation { } } -/// Risk calculation utilities +/// Trading-specific risk calculation utilities +/// For basic risk calculations, use the shared `risk` crate pub mod risk { use super::*; - /// Risk calculator for position management - pub struct RiskCalculator { - max_position_value: f64, - max_daily_loss: f64, - max_drawdown: f64, - risk_free_rate: f64, + /// Position risk metrics specific to trading service + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct PositionRisk { + pub position_value: f64, + pub portfolio_value: f64, + pub position_ratio: f64, + pub risk_score: f64, + pub is_over_limit: bool, } - impl RiskCalculator { - pub fn new( - max_position_value: f64, - max_daily_loss: f64, - max_drawdown: f64, - risk_free_rate: f64, - ) -> Self { - Self { - max_position_value, - max_daily_loss, - max_drawdown, - risk_free_rate, - } + /// Trading-specific risk calculations that extend shared risk library + pub struct TradingRiskCalculator { + max_position_value: f64, + } + + impl TradingRiskCalculator { + pub fn new(max_position_value: f64) -> Self { + Self { max_position_value } } - /// Calculate position risk metrics + /// Calculate position risk metrics specific to trading pub fn calculate_position_risk( &self, position_value: f64, @@ -220,96 +206,44 @@ pub mod risk { is_over_limit: position_value > self.max_position_value, } } - - /// Calculate Value at Risk (VaR) - pub fn calculate_var( - &self, - position_value: f64, - volatility: f64, - confidence_level: f64, - ) -> f64 { - // Simple parametric VaR calculation - // VaR = position_value * z_score * volatility * sqrt(time_horizon) - let z_score = match confidence_level { - 0.95 => 1.645, - 0.99 => 2.326, - _ => 1.96, // Default to 95% confidence - }; - - let time_horizon = 1.0; // 1 day - position_value * z_score * volatility * time_horizon.sqrt() - } - - /// Calculate maximum allowed position size based on risk - pub fn calculate_max_position_size(&self, price: f64, volatility: f64) -> f64 { - let var_limit = self.max_daily_loss; - let z_score = 1.96; // 95% confidence - - if volatility > 0.0 && price > 0.0 { - var_limit / (z_score * volatility * price) - } else { - self.max_position_value / price - } - } - - /// Calculate Sharpe ratio - pub fn calculate_sharpe_ratio(&self, returns: &[f64]) -> f64 { - if returns.is_empty() { - return 0.0; - } - - let mean_return = returns.iter().sum::() / returns.len() as f64; - let variance = returns - .iter() - .map(|r| (r - mean_return).powi(2)) - .sum::() - / returns.len() as f64; - let std_dev = variance.sqrt(); - - if std_dev > 0.0 { - (mean_return - self.risk_free_rate) / std_dev - } else { - 0.0 - } - } } - impl Default for RiskCalculator { + impl Default for TradingRiskCalculator { fn default() -> Self { - Self::new( - 100_000.0, // max_position_value - 10_000.0, // max_daily_loss - 20_000.0, // max_drawdown - 0.02, // risk_free_rate (2%) - ) + Self::new(100_000.0) // Default max position value } } - - /// Position risk metrics - #[derive(Debug, Clone, Serialize, Deserialize)] - pub struct PositionRisk { - pub position_value: f64, - pub portfolio_value: f64, - pub position_ratio: f64, - pub risk_score: f64, - pub is_over_limit: bool, - } } -/// Performance monitoring for trading operations +/// Trading-specific performance monitoring +/// Note: Basic metrics are available in the common library via Metrics trait pub mod monitoring { use super::*; + use std::sync::atomic::{AtomicU64, Ordering}; + use std::time::Instant; - /// Trading performance metrics collector + /// Trading-specific metrics that extend common metrics + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct TradingMetricsSnapshot { + pub order_count: u64, + pub fill_count: u64, + pub cancel_count: u64, + pub reject_count: u64, + pub total_volume: f64, + pub total_pnl: f64, + pub uptime_seconds: u64, + pub fill_rate: f64, + pub orders_per_second: f64, + } + + /// Simplified trading metrics collector + /// For advanced metrics, consider using common::traits::Metrics #[derive(Debug, Clone)] pub struct TradingMetrics { order_count: AtomicU64, fill_count: AtomicU64, cancel_count: AtomicU64, reject_count: AtomicU64, - total_volume: Arc>, - total_pnl: Arc>, - latency_stats: Arc>, start_time: Instant, } @@ -320,9 +254,6 @@ pub mod monitoring { fill_count: AtomicU64::new(0), cancel_count: AtomicU64::new(0), reject_count: AtomicU64::new(0), - total_volume: Arc::new(parking_lot::RwLock::new(0.0)), - total_pnl: Arc::new(parking_lot::RwLock::new(0.0)), - latency_stats: Arc::new(parking_lot::RwLock::new(LatencyStats::new())), start_time: Instant::now(), } } @@ -333,18 +264,8 @@ pub mod monitoring { } /// Record order fill - pub fn record_fill(&self, volume: f64, pnl: f64) { + pub fn record_fill(&self) { self.fill_count.fetch_add(1, Ordering::Relaxed); - - { - let mut total_vol = self.total_volume.write(); - *total_vol += volume; - } - - { - let mut total_pnl = self.total_pnl.write(); - *total_pnl += pnl; - } } /// Record order cancellation @@ -357,49 +278,26 @@ pub mod monitoring { self.reject_count.fetch_add(1, Ordering::Relaxed); } - /// Record order latency - pub fn record_latency(&self, latency_micros: u64) { - let mut stats = self.latency_stats.write(); - stats.record(latency_micros); - } - - /// Get current metrics snapshot + /// Get basic trading metrics pub fn get_snapshot(&self) -> TradingMetricsSnapshot { let uptime = self.start_time.elapsed(); - - TradingMetricsSnapshot { - order_count: self.order_count.load(Ordering::Relaxed), - fill_count: self.fill_count.load(Ordering::Relaxed), - cancel_count: self.cancel_count.load(Ordering::Relaxed), - reject_count: self.reject_count.load(Ordering::Relaxed), - total_volume: *self.total_volume.read(), - total_pnl: *self.total_pnl.read(), - latency_stats: self.latency_stats.read().clone(), - uptime_seconds: uptime.as_secs(), - fill_rate: self.calculate_fill_rate(), - orders_per_second: self.calculate_orders_per_second(uptime), - } - } - - fn calculate_fill_rate(&self) -> f64 { let orders = self.order_count.load(Ordering::Relaxed); let fills = self.fill_count.load(Ordering::Relaxed); - if orders > 0 { - fills as f64 / orders as f64 - } else { - 0.0 - } - } - - fn calculate_orders_per_second(&self, uptime: Duration) -> f64 { - let orders = self.order_count.load(Ordering::Relaxed); - let seconds = uptime.as_secs_f64(); - - if seconds > 0.0 { - orders as f64 / seconds - } else { - 0.0 + TradingMetricsSnapshot { + order_count: orders, + fill_count: fills, + cancel_count: self.cancel_count.load(Ordering::Relaxed), + reject_count: self.reject_count.load(Ordering::Relaxed), + total_volume: 0.0, // Would be calculated externally + total_pnl: 0.0, // Would be calculated externally + uptime_seconds: uptime.as_secs(), + fill_rate: if orders > 0 { fills as f64 / orders as f64 } else { 0.0 }, + orders_per_second: if uptime.as_secs_f64() > 0.0 { + orders as f64 / uptime.as_secs_f64() + } else { + 0.0 + }, } } } @@ -409,185 +307,21 @@ pub mod monitoring { Self::new() } } - - /// Latency statistics tracking - #[derive(Debug, Clone)] - pub struct LatencyStats { - count: u64, - sum: u64, - min: u64, - max: u64, - values: Vec, // Keep recent values for percentile calculation - } - - impl LatencyStats { - pub fn new() -> Self { - Self { - count: 0, - sum: 0, - min: u64::MAX, - max: 0, - values: Vec::new(), - } - } - - pub fn record(&mut self, latency_micros: u64) { - self.count += 1; - self.sum += latency_micros; - self.min = self.min.min(latency_micros); - self.max = self.max.max(latency_micros); - - // Keep only recent 1000 values for percentile calculation - self.values.push(latency_micros); - if self.values.len() > 1000 { - self.values.remove(0); - } - } - - pub fn mean(&self) -> f64 { - if self.count > 0 { - self.sum as f64 / self.count as f64 - } else { - 0.0 - } - } - - pub fn percentile(&self, p: f64) -> u64 { - if self.values.is_empty() { - return 0; - } - - let mut sorted = self.values.clone(); - sorted.sort_unstable(); - - let index = ((p / 100.0) * (sorted.len() - 1) as f64).round() as usize; - sorted[index.min(sorted.len() - 1)] - } - } - - /// Trading metrics snapshot - #[derive(Debug, Clone, Serialize, Deserialize)] - pub struct TradingMetricsSnapshot { - pub order_count: u64, - pub fill_count: u64, - pub cancel_count: u64, - pub reject_count: u64, - pub total_volume: f64, - pub total_pnl: f64, - pub latency_stats: LatencyStats, - pub uptime_seconds: u64, - pub fill_rate: f64, - pub orders_per_second: f64, - } } -/// Portfolio calculation utilities +/// Trading-specific position tracking +/// Note: For advanced portfolio analytics, consider integrating with shared libraries pub mod portfolio { use super::*; + use std::collections::HashMap; - /// Portfolio position tracker - #[derive(Debug, Clone)] - pub struct PositionTracker { - positions: Arc>>, - pnl_history: Arc>>, - } - - impl PositionTracker { - pub fn new() -> Self { - Self { - positions: Arc::new(parking_lot::RwLock::new(HashMap::new())), - pnl_history: Arc::new(parking_lot::RwLock::new(Vec::new())), - } - } - - /// Update position for a symbol - pub fn update_position(&self, symbol: &str, quantity: f64, price: f64) { - let mut positions = self.positions.write(); - let position = positions - .entry(symbol.to_string()) - .or_insert_with(Position::new); - position.update(quantity, price); - } - - /// Get position for a symbol - pub fn get_position(&self, symbol: &str) -> Option { - self.positions.read().get(symbol).cloned() - } - - /// Get all positions - pub fn get_all_positions(&self) -> HashMap { - self.positions.read().clone() - } - - /// Calculate total portfolio value - pub fn calculate_portfolio_value(&self, market_prices: &HashMap) -> f64 { - let positions = self.positions.read(); - - positions - .iter() - .map(|(symbol, position)| { - if let Some(&market_price) = market_prices.get(symbol) { - position.quantity * market_price - } else { - position.quantity * position.avg_price - } - }) - .sum() - } - - /// Calculate unrealized P&L - pub fn calculate_unrealized_pnl(&self, market_prices: &HashMap) -> f64 { - let positions = self.positions.read(); - - positions - .iter() - .map(|(symbol, position)| { - if let Some(&market_price) = market_prices.get(symbol) { - position.quantity * (market_price - position.avg_price) - } else { - 0.0 - } - }) - .sum() - } - - /// Record P&L snapshot - pub fn record_pnl_snapshot(&self, realized_pnl: f64, unrealized_pnl: f64) { - let snapshot = PnlSnapshot { - timestamp: Utc::now(), - realized_pnl, - unrealized_pnl, - total_pnl: realized_pnl + unrealized_pnl, - }; - - let mut history = self.pnl_history.write(); - history.push(snapshot); - - // Keep only last 1000 snapshots - if history.len() > 1000 { - history.remove(0); - } - } - - /// Get P&L history - pub fn get_pnl_history(&self) -> Vec { - self.pnl_history.read().clone() - } - } - - impl Default for PositionTracker { - fn default() -> Self { - Self::new() - } - } - - /// Position information for a single symbol + /// Simplified position information for a single symbol #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Position { pub quantity: f64, pub avg_price: f64, pub realized_pnl: f64, - pub last_update: DateTime, + pub last_update: Timestamp, } impl Position { @@ -596,10 +330,11 @@ pub mod portfolio { quantity: 0.0, avg_price: 0.0, realized_pnl: 0.0, - last_update: Utc::now(), + last_update: chrono::Utc::now(), } } + /// Update position with new trade pub fn update(&mut self, quantity_change: f64, price: f64) { if quantity_change == 0.0 { return; @@ -619,7 +354,7 @@ pub mod portfolio { self.avg_price = total_cost / new_quantity; self.quantity = new_quantity; } else { - // Reducing or closing position + // Reducing or closing position - calculate realized PnL let closed_quantity = quantity_change.abs().min(self.quantity.abs()); let pnl_per_share = if self.quantity > 0.0 { price - self.avg_price @@ -636,28 +371,42 @@ pub mod portfolio { } } - self.last_update = Utc::now(); + self.last_update = chrono::Utc::now(); + } + + /// Calculate unrealized PnL based on current market price + pub fn unrealized_pnl(&self, market_price: f64) -> f64 { + if self.quantity == 0.0 { + 0.0 + } else { + self.quantity * (market_price - self.avg_price) + } + } + } + + impl Default for Position { + fn default() -> Self { + Self::new() } } /// P&L snapshot at a point in time #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PnlSnapshot { - pub timestamp: DateTime, + pub timestamp: Timestamp, pub realized_pnl: f64, pub unrealized_pnl: f64, pub total_pnl: f64, } } -/// Utility functions for trading operations +/// Trading-specific utility functions pub mod helpers { use super::*; + use std::sync::atomic::{AtomicU64, Ordering}; /// Generate unique order ID pub fn generate_order_id() -> String { - use std::sync::atomic::{AtomicU64, Ordering}; - static ORDER_COUNTER: AtomicU64 = AtomicU64::new(0); let counter = ORDER_COUNTER.fetch_add(1, Ordering::Relaxed); let timestamp = std::time::SystemTime::now() @@ -673,7 +422,6 @@ pub mod helpers { if tick_size <= 0.0 { return price; } - (price / tick_size).round() * tick_size } @@ -682,38 +430,26 @@ pub mod helpers { quantity.abs() * price } - /// Format price for display with appropriate precision + /// Format price for trading display pub fn format_price(price: f64, symbol: &str) -> String { - // Most forex pairs use 5 decimal places, others use 2-4 let decimals = if symbol.len() == 6 && symbol.chars().all(|c| c.is_ascii_alphabetic()) { 5 // Forex pair } else { 2 // Stock/commodity }; - format!("{:.decimals$}", price, decimals = decimals) } - /// Calculate commission based on order details - pub fn calculate_commission(quantity: f64, price: f64, commission_rate: f64) -> f64 { - let order_value = calculate_order_value(quantity, price); - order_value * commission_rate - } - - /// Validate if market is open (simplified) + /// Simple market hours check (extend as needed) pub fn is_market_open() -> bool { - use chrono::{Timelike, Utc, Weekday}; - - let now = Utc::now(); + let now = chrono::Utc::now(); let weekday = now.weekday(); let hour = now.hour(); - // Simplified: Monday to Friday, 9 AM to 4 PM UTC matches!( weekday, - Weekday::Mon | Weekday::Tue | Weekday::Wed | Weekday::Thu | Weekday::Fri - ) && hour >= 9 - && hour < 16 + chrono::Weekday::Mon | chrono::Weekday::Tue | chrono::Weekday::Wed | chrono::Weekday::Thu | chrono::Weekday::Fri + ) && hour >= 9 && hour < 16 } } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 68c963226..f07368ec4 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -29,7 +29,7 @@ pub mod models; // Re-export commonly used types and traits pub use error::{StorageError, StorageResult}; -pub use foxhunt_config::StorageConfig; +pub use foxhunt-config::StorageConfig; #[cfg(feature = "s3")] pub use s3::{S3Storage, S3StorageConfig, ArchivalDataType, ArchivalMetadata, ArchivalStats}; diff --git a/tests/lib.rs b/tests/lib.rs index 81bd16b1e..a504e5534 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -347,7 +347,7 @@ pub mod config { pub use common::*; // pub use framework::*; // pub use helpers::*; -pub use foxhunt_config::*; +pub use foxhunt-config::*; pub use mocks::*; pub use performance_utils::*; pub use safety::*; diff --git a/tli/src/auth/audit.rs b/tli/src/auth/audit.rs index de8235c8a..0a28b64f9 100644 --- a/tli/src/auth/audit.rs +++ b/tli/src/auth/audit.rs @@ -171,8 +171,11 @@ impl AuditLogger { reason: format!("Failed to open audit log file: {}", e), })?; + // Store encryption setting before moving config + let encrypt_logs = config.encrypt_logs; + // Initialize encryption if enabled - let encryption_key = if config.encrypt_logs { + let encryption_key = if encrypt_logs { let key_bytes = Self::generate_encryption_key()?; let unbound_key = UnboundKey::new(&AES_256_GCM, &key_bytes) .map_err(|e| AuditError::EncryptionError { @@ -182,7 +185,7 @@ impl AuditLogger { } else { None }; - + let logger = Self { config, log_file: Arc::new(Mutex::new(log_file)), @@ -197,7 +200,7 @@ impl AuditLogger { compliance_violations: 0, })), }; - + // Log audit system initialization logger.log_system_event( AuditEventType::SystemStartup, @@ -206,8 +209,8 @@ impl AuditLogger { "Audit logging system started", HashMap::new(), ).await?; - - info!("Audit logger initialized with encryption: {}", config.encrypt_logs); + + info!("Audit logger initialized with encryption: {}", encrypt_logs); Ok(logger) } @@ -605,20 +608,22 @@ impl AuditLogger { .map_err(|e| AuditError::EncryptionError { reason: format!("Failed to generate nonce: {}", e), })?; - + + // Store nonce bytes before creating Nonce (which will be consumed) + let nonce_bytes_copy = nonce_bytes.clone(); let nonce = Nonce::assume_unique_for_key(nonce_bytes.try_into().unwrap()); let aad = Aad::empty(); - + let mut ciphertext = plaintext.as_bytes().to_vec(); key.seal_in_place_append_tag(nonce, aad, &mut ciphertext) .map_err(|e| AuditError::EncryptionError { reason: format!("Encryption failed: {}", e), })?; - - // Prepend nonce to ciphertext - let mut result = nonce.as_ref().to_vec(); + + // Prepend nonce to ciphertext using the copy + let mut result = nonce_bytes_copy; result.extend_from_slice(&ciphertext); - + Ok(result) } else { Err(AuditError::EncryptionError { diff --git a/tli/src/dashboard/mod.rs b/tli/src/dashboard/mod.rs index ca88afe53..aaae1723a 100644 --- a/tli/src/dashboard/mod.rs +++ b/tli/src/dashboard/mod.rs @@ -29,7 +29,7 @@ pub mod trading; pub mod vault_status; pub use backtesting::BacktestingDashboard; -pub use foxhunt_config::ConfigDashboard; +pub use foxhunt-config::ConfigDashboard; pub use events::*; pub use layout::LayoutManager; pub use ml::MLDashboard;