Codebase audit identified 23 findings across 4 dimensions (production safety, code health, deployment readiness, test quality). This commit fixes all of them. Broker execution layer (was entirely stubbed): - Real IBKR TWS client via ibapi crate (950+ lines, feature-gated) - ICMarkets ctrader-openapi now always-on (removed feature flag) - Real broker routing with health monitoring and exponential backoff reconnect - Validated against live IB Gateway Docker (6/6 connectivity tests pass) Deployment blockers: - Fixed 6 broken Dockerfiles (removed COPY foxhunt-deploy) - Created foxhunt K8s namespace, secret templates, migration job - Added liveness probes to all 7 K8s services - IB Gateway manifest (ghcr.io/gnzsnz/ib-gateway:stable) - IBKR credentials in Scaleway Secret Manager via Terragrunt - Fixed port collisions and mismatches across services Production safety (9 critical + 6 high/medium fixes): - Asset-class-specific VaR volatility (not flat 2%) - Real parametric VaR with z-score 95th percentile - Kyle's lambda regression (100-bar rolling window) - Per-feature running statistics from historical data - VWAP-based slippage reference, regime duration tracking - Real Databento JSON parsing for OHLCV/Trade/Quote Code health: - Removed #![allow(dead_code)] from ml, data, config - Fixed log:: → tracing:: in 4 production files - Removed dead workspace deps (ratatui, crossterm) Verified: cargo check --workspace (0 errors), trading_engine 330 tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2078 lines
71 KiB
Rust
2078 lines
71 KiB
Rust
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
|
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
|
|
#![allow(missing_docs)] // Internal implementation details don't require documentation
|
|
#![allow(missing_debug_implementations)] // Not all types need Debug
|
|
#![allow(unused_crate_dependencies)] // Dev dependencies not used in lib.rs
|
|
#![allow(clippy::float_arithmetic)] // ML operations require float arithmetic
|
|
// ML-specific lint overrides: these are intentional domain patterns, not safety issues
|
|
#![allow(clippy::non_ascii_literal)] // Box-drawing chars for tables, Greek letters for math
|
|
#![allow(clippy::str_to_string)] // Pervasive in ML config/display code, not a safety concern
|
|
#![allow(clippy::partial_pub_fields)] // ML model structs mix pub config with private state
|
|
#![allow(clippy::multiple_inherent_impl)] // Split impls for readability (core vs Display vs Builder)
|
|
#![allow(clippy::same_name_method)] // Trait methods intentionally shadow inherent methods
|
|
#![allow(clippy::shadow_reuse)] // Tensor code naturally shadows: let x = relu(x)
|
|
#![allow(clippy::shadow_unrelated)] // ML pipeline variable reuse across phases
|
|
#![allow(clippy::shadow_same)] // Rebinding after narrowing in match/if-let
|
|
// ML domain pedantic lints: these are noise in numerical/ML code, not safety issues
|
|
// Individual pedantic lints are managed at workspace level in Cargo.toml
|
|
#![allow(clippy::doc_markdown)] // Technical terms (AVX2, SIMD, HFT, etc.) in doc comments
|
|
#![allow(clippy::indexing_slicing)] // Tensor/array indexing is pervasive and bounds-checked in context
|
|
#![allow(clippy::missing_const_for_fn)] // Const fn not critical for ML model code
|
|
#![allow(clippy::module_name_repetitions)] // Module-prefixed types provide clarity (DQNAgent, TFTTrainer)
|
|
#![allow(clippy::integer_division)] // Integer division is intentional in batch/epoch calculations
|
|
#![allow(clippy::cognitive_complexity)] // ML training loops and model architectures are inherently complex
|
|
#![allow(clippy::similar_names)] // ML variables often have similar names (x, xs, x_hat, x_norm)
|
|
#![allow(clippy::clone_on_ref_ptr)] // Arc::clone is intentional for shared model state
|
|
#![allow(clippy::too_many_lines)] // Complex model implementations need many lines
|
|
#![allow(clippy::as_conversions)] // Necessary for tensor dimension/type conversions
|
|
#![allow(clippy::cast_precision_loss)] // Acceptable in ML with f32/f64 conversions
|
|
#![allow(clippy::cast_possible_truncation)] // Type conversions validated in context
|
|
#![allow(clippy::default_numeric_fallback)] // Float/int literals are contextually typed in ML code
|
|
#![allow(clippy::arithmetic_side_effects)] // ML math uses checked/saturating where needed
|
|
#![allow(clippy::needless_range_loop)] // Index-based loops often clearer for tensor operations
|
|
#![allow(clippy::into_iter_on_ref)] // .iter() vs .into_iter() on refs is stylistic
|
|
#![allow(clippy::new_without_default)] // Many ML types need config params, Default is inappropriate
|
|
#![allow(clippy::manual_let_else)] // if-let pattern preferred in many ML error paths
|
|
#![allow(clippy::unnecessary_wraps)] // Result/Option wrapping needed for trait consistency
|
|
#![allow(clippy::too_many_arguments)] // ML functions often need many hyperparameters
|
|
#![allow(clippy::must_use_candidate)] // Not all ML functions need must_use
|
|
#![allow(clippy::missing_errors_doc)] // Internal ML APIs don't need full error documentation
|
|
#![allow(clippy::cast_sign_loss)] // Sign loss validated in context
|
|
#![allow(clippy::cast_possible_wrap)] // Wrap validated in context
|
|
#![allow(clippy::cast_lossless)] // as casts are intentional for ML numeric conversions
|
|
#![allow(clippy::unused_async)] // Async needed for trait implementations
|
|
#![allow(clippy::match_same_arms)] // Explicit match arms preferred for clarity in ML code
|
|
#![allow(clippy::unused_self)] // Self parameter needed for trait consistency
|
|
#![allow(clippy::map_err_ignore)] // Error conversion doesn't need original context in ML pipelines
|
|
#![allow(clippy::single_match_else)] // Explicit match preferred for clarity in ML code
|
|
#![allow(clippy::wildcard_imports)] // Prelude-style imports common in ML modules
|
|
#![allow(clippy::unnecessary_cast)] // Explicit casts for tensor dimension/type clarity
|
|
#![allow(clippy::undocumented_unsafe_blocks)] // Unsafe blocks documented at usage site
|
|
#![allow(clippy::redundant_clone)] // Clones needed for ownership in async/parallel ML pipelines
|
|
#![allow(clippy::redundant_closure)] // Explicit closures preferred for readability
|
|
#![allow(clippy::type_complexity)] // Complex types unavoidable in ML generics
|
|
#![allow(clippy::manual_clamp)] // Explicit min/max preferred in numerical code
|
|
#![allow(clippy::clone_on_copy)] // Explicit clone for clarity on Copy types
|
|
#![allow(clippy::should_implement_trait)] // Custom builder patterns don't need std traits
|
|
#![allow(clippy::derivable_impls)] // Custom Default impls with domain-specific values
|
|
#![allow(clippy::useless_conversion)] // Explicit conversions for type clarity
|
|
#![allow(clippy::get_first)] // .get(0) preferred for consistency with .get(n)
|
|
#![allow(clippy::len_zero)] // .len() == 0 preferred for readability in some contexts
|
|
#![allow(clippy::assign_op_pattern)] // Explicit assignment preferred in numerical code
|
|
#![allow(clippy::if_same_then_else)] // Intentional identical branches for documentation
|
|
#![allow(clippy::unused_enumerate_index)] // Index used in debug/logging contexts
|
|
#![allow(clippy::doc_lazy_continuation)] // Doc formatting acceptable
|
|
#![allow(clippy::doc_overindented_list_items)] // Doc formatting acceptable
|
|
#![allow(clippy::single_char_add_str)] // String building patterns
|
|
#![allow(clippy::let_and_return)] // Named return values for clarity
|
|
#![allow(clippy::useless_format)] // Explicit format for consistency
|
|
#![allow(clippy::manual_div_ceil)] // Explicit ceiling division for clarity
|
|
#![allow(clippy::io_other_error)] // IO error construction patterns
|
|
#![allow(clippy::manual_range_contains)] // Explicit range checks for clarity
|
|
#![allow(clippy::unwrap_or_default)] // Explicit unwrap_or preferred in some contexts
|
|
#![allow(clippy::used_underscore_binding)] // Underscore-prefixed bindings used intentionally
|
|
#![allow(clippy::trivially_copy_pass_by_ref)] // Pass-by-ref for trait consistency
|
|
#![allow(clippy::needless_borrows_for_generic_args)] // Explicit borrows for clarity
|
|
#![allow(clippy::needless_borrow)] // Explicit borrows for clarity
|
|
#![allow(clippy::missing_safety_doc)] // Safety documented at usage site
|
|
#![allow(clippy::module_inception)] // Module name matches parent for re-export patterns
|
|
#![allow(clippy::if_not_else)] // Negated conditions preferred in some ML logic
|
|
#![allow(clippy::empty_line_after_doc_comments)] // Doc comment formatting acceptable
|
|
#![allow(clippy::unnecessary_lazy_evaluations)] // Explicit lazy evaluation for side effects
|
|
#![allow(clippy::collapsible_if)] // Separate ifs preferred for readability
|
|
#![allow(clippy::question_mark)] // Explicit error handling preferred
|
|
#![allow(clippy::op_ref)] // Explicit ref operations for numeric types
|
|
#![allow(clippy::iter_kv_map)] // Map iteration patterns
|
|
#![allow(clippy::ptr_arg)] // Ptr args for trait consistency
|
|
#![allow(clippy::needless_question_mark)] // Explicit ? for error propagation clarity
|
|
#![allow(clippy::explicit_auto_deref)] // Explicit derefs for clarity
|
|
#![allow(clippy::bind_instead_of_map)] // Bind preferred in some combinator chains
|
|
#![allow(clippy::upper_case_acronyms)] // ML acronyms (DQN, PPO, TFT, etc.)
|
|
#![allow(clippy::large_types_passed_by_value)] // Large types passed by value for ownership
|
|
#![allow(clippy::large_enum_variant)] // Large variants unavoidable in ML model enums
|
|
#![allow(clippy::collapsible_else_if)] // Separate else-if preferred for readability
|
|
#![allow(clippy::vec_init_then_push)] // Vec init then push for conditional building
|
|
#![allow(clippy::implicit_saturating_sub)] // Explicit subtraction preferred
|
|
#![allow(clippy::missing_fields_in_debug)] // Custom Debug impls for large ML structs
|
|
#![allow(clippy::field_reassign_with_default)] // Field reassignment after Default::default()
|
|
#![allow(clippy::len_without_is_empty)] // len() without is_empty() for ML containers
|
|
#![allow(clippy::iter_nth)] // .nth() for indexed iteration
|
|
#![allow(clippy::items_after_statements)] // Items after statements for local helpers
|
|
#![allow(clippy::uninlined_format_args)] // Non-inlined format args for readability
|
|
#![allow(clippy::manual_memcpy)] // Explicit loop copy for tensor operations
|
|
#![allow(clippy::single_char_lifetime_names)] // Standard Rust lifetime conventions in ODE solvers
|
|
#![allow(clippy::same_item_push)] // Intentional repeated pushes for sequence padding
|
|
#![allow(clippy::multiple_unsafe_ops_per_block)] // SIMD/hardware ops grouped for clarity
|
|
#![allow(clippy::arc_with_non_send_sync)] // Arc used for local-thread inference contexts
|
|
#![allow(clippy::format_in_format_args)] // Nested format for dynamic message building
|
|
#![allow(clippy::inherent_to_string)] // Custom to_string for display types
|
|
#![allow(clippy::redundant_locals)] // Rebinding for clarity in model pipelines
|
|
#![allow(clippy::to_string_trait_impl)] // Direct ToString impls for versioning types
|
|
#![recursion_limit = "256"] // Required for complex TFT quantile operations
|
|
//! Machine Learning Models for Foxhunt
|
|
//!
|
|
//! This crate provides comprehensive machine learning models and algorithms
|
|
//! for the Foxhunt high-frequency trading system. All ML operations use
|
|
//! enterprise-grade safety controls to prevent system failures.
|
|
//!
|
|
//! ## Safety Features
|
|
//!
|
|
//! - **Comprehensive mathematical safety**: All operations handle NaN/Infinity gracefully
|
|
//! - **Tensor bounds checking**: Prevents buffer overflows and memory issues
|
|
//! - **Model drift detection**: Automatic monitoring of model performance degradation
|
|
//! - **Financial validation**: Ensures all predictions use unified financial types
|
|
//! - **Memory management**: Prevents OOM conditions and memory leaks
|
|
//! - **Timeout handling**: Prevents hanging operations
|
|
//!
|
|
//! ## Usage
|
|
//!
|
|
//! ```no_run
|
|
//! // ML safety manager usage example
|
|
//! // Note: This is a conceptual example - actual implementation may vary
|
|
//! use ml::safety::MLSafetyConfig;
|
|
//!
|
|
//! #[tokio::main]
|
|
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
//! // Initialize safety with custom configuration
|
|
//! let _config = MLSafetyConfig::default();
|
|
//!
|
|
//! // ML operations would use the safety manager
|
|
//! // (actual implementation details depend on the safety module structure)
|
|
//! Ok(())
|
|
//! }
|
|
//! ```
|
|
|
|
#![warn(missing_debug_implementations)]
|
|
#![warn(rust_2018_idioms)]
|
|
// NOTE: clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing
|
|
// are governed by workspace lints at "warn" level. The remaining lints below
|
|
// are already "deny" at workspace level -- kept here for explicitness.
|
|
#![deny(clippy::panic, clippy::unimplemented, clippy::unreachable)]
|
|
|
|
// Import common types properly - NO ALIASES THAT CONFLICT!
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
// Silence unused crate warnings for dependencies used in tests or feature-gated code
|
|
use approx as _;
|
|
use bincode as _;
|
|
use half as _;
|
|
use memmap2 as _;
|
|
use num as _;
|
|
use num_traits as _;
|
|
use semver as _;
|
|
use tempfile as _;
|
|
use trading_engine as _;
|
|
|
|
// Direct type imports - no compatibility aliases
|
|
use rust_decimal::Decimal;
|
|
|
|
/// Common type errors that can occur during ML operations
|
|
///
|
|
/// This enum represents various type-related errors that can occur when working
|
|
/// with different data types across the ML pipeline, including type conversions,
|
|
/// validation errors, and compatibility issues.
|
|
#[derive(Debug, Clone, thiserror::Error, serde::Serialize, serde::Deserialize)]
|
|
pub enum CommonTypeError {
|
|
/// Generic type error with descriptive message
|
|
#[error("Type error: {0}")]
|
|
Error(String),
|
|
}
|
|
|
|
/// Market regime classification for algorithmic trading strategies
|
|
///
|
|
/// This enum represents different market conditions that can be detected through
|
|
/// statistical analysis and machine learning models. Market regime detection is
|
|
/// crucial for adaptive trading strategies that adjust their behavior based on
|
|
/// current market conditions.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// use ml::MarketRegime;
|
|
///
|
|
/// let regime = MarketRegime::Trending;
|
|
/// match regime {
|
|
/// MarketRegime::Bull => println!("Use momentum strategies"),
|
|
/// MarketRegime::Bear => println!("Use defensive strategies"),
|
|
/// MarketRegime::Crisis => println!("Implement risk controls"),
|
|
/// _ => println!("Use balanced approach"),
|
|
/// }
|
|
/// ```
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub enum MarketRegime {
|
|
/// Normal market conditions with typical volatility and volume
|
|
Normal,
|
|
/// Strong directional movement with clear trends
|
|
Trending,
|
|
/// Range-bound market with limited directional movement
|
|
Sideways,
|
|
/// Bullish market with rising prices and positive sentiment
|
|
Bull,
|
|
/// Bearish market with falling prices and negative sentiment
|
|
Bear,
|
|
/// Crisis conditions with extreme volatility and risk
|
|
Crisis,
|
|
}
|
|
|
|
/// Common errors that can occur across the ML system
|
|
///
|
|
/// This enum provides a unified error type that can be used throughout the ML
|
|
/// pipeline to ensure consistent error handling and reporting. It serves as a
|
|
/// bridge between different subsystems and provides appropriate error categorization.
|
|
#[derive(Debug, Clone, thiserror::Error, serde::Serialize, serde::Deserialize)]
|
|
pub enum CommonError {
|
|
/// General error with descriptive message
|
|
#[error("Error: {0}")]
|
|
General(String),
|
|
}
|
|
|
|
impl CommonError {
|
|
/// Create a validation error with a descriptive message
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `msg` - A descriptive message explaining the validation failure
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Returns a `CommonError::General` variant with the validation message
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// use ml::CommonError;
|
|
///
|
|
/// let error = CommonError::validation("Invalid input range");
|
|
/// assert!(error.to_string().contains("Invalid input range"));
|
|
/// ```
|
|
pub fn validation<S: Into<String>>(msg: S) -> Self {
|
|
Self::General(msg.into())
|
|
}
|
|
|
|
/// Create a configuration error with a descriptive message
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `msg` - A descriptive message explaining the configuration issue
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Returns a `CommonError::General` variant with the configuration message
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// use ml::CommonError;
|
|
///
|
|
/// let error = CommonError::config("Missing required parameter");
|
|
/// assert!(error.to_string().contains("Missing required parameter"));
|
|
/// ```
|
|
pub fn config<S: Into<String>>(msg: S) -> Self {
|
|
Self::General(msg.into())
|
|
}
|
|
|
|
/// Create a service error with category and descriptive message
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `category` - The error category to classify the service error
|
|
/// * `msg` - A descriptive message explaining the service issue
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Returns a `CommonError::General` variant with the categorized service message
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// use ml::{CommonError, ErrorCategory};
|
|
///
|
|
/// let error = CommonError::service(ErrorCategory::System, "Database connection failed");
|
|
/// assert!(error.to_string().contains("System"));
|
|
/// assert!(error.to_string().contains("Database connection failed"));
|
|
/// ```
|
|
pub fn service<S: Into<String>>(category: ErrorCategory, msg: S) -> Self {
|
|
Self::General(format!("{:?}: {}", category, msg.into()))
|
|
}
|
|
}
|
|
|
|
// Re-export canonical ErrorCategory from common crate (24 variants)
|
|
pub use ::common::error::ErrorCategory;
|
|
|
|
// Re-export Adam optimizer (moved from inline to optimizers/adam.rs)
|
|
pub use optimizers::Adam;
|
|
|
|
// Now using real types from common crate
|
|
|
|
// Core ML types
|
|
/// Represents a financial trade for ML model training and analysis
|
|
///
|
|
/// This structure contains the essential information about a trade that is used
|
|
/// by ML models for pattern recognition, market analysis, and strategy optimization.
|
|
/// The structure is optimized for both in-memory processing and database storage.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// use ml::Trade;
|
|
/// use rust_decimal::Decimal;
|
|
///
|
|
/// let trade = Trade {
|
|
/// symbol: "AAPL".to_owned(),
|
|
/// price: Decimal::new(15000, 2), // $150.00
|
|
/// quantity: Decimal::new(100, 0), // 100 shares
|
|
/// timestamp: 1640995200000000, // microseconds since epoch
|
|
/// side: "buy".to_owned(),
|
|
/// };
|
|
/// ```
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Trade {
|
|
/// Trading symbol (e.g., "AAPL", "MSFT")
|
|
pub symbol: String,
|
|
/// Trade price in decimal format for precision
|
|
pub price: Decimal,
|
|
/// Trade quantity in decimal format for precision
|
|
pub quantity: Decimal,
|
|
/// Timestamp in microseconds since Unix epoch
|
|
pub timestamp: u64,
|
|
/// Trade side: "buy" or "sell"
|
|
pub side: String,
|
|
}
|
|
|
|
/// Health status for ensemble models and ML system components
|
|
///
|
|
/// This enum tracks the operational status of ML models and system components,
|
|
/// enabling automated health monitoring, alerting, and failover mechanisms.
|
|
/// Health status is crucial for maintaining system reliability in production.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// use ml::HealthStatus;
|
|
///
|
|
/// let status = HealthStatus::Healthy;
|
|
/// match status {
|
|
/// HealthStatus::Healthy => println!("System operating normally"),
|
|
/// HealthStatus::Degraded => println!("Performance below optimal"),
|
|
/// HealthStatus::Unhealthy => println!("System requires intervention"),
|
|
/// }
|
|
/// ```
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum HealthStatus {
|
|
/// Component is operating within normal parameters
|
|
Healthy,
|
|
/// Component is operational but performance is below optimal
|
|
Degraded,
|
|
/// Component is not functioning properly and requires intervention
|
|
Unhealthy,
|
|
}
|
|
|
|
// Import specific types from trading_engine that we need
|
|
// (removed wildcard prelude to avoid conflicts)
|
|
|
|
// Using Decimal for financial types
|
|
|
|
/// Market data snapshot for ML model input
|
|
///
|
|
/// Represents a point-in-time snapshot of market data that serves as input
|
|
/// for ML models. This structure contains the essential market information
|
|
/// needed for real-time trading decisions and model inference.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// use ml::MarketDataSnapshot;
|
|
/// use rust_decimal::Decimal;
|
|
/// use chrono::Utc;
|
|
///
|
|
/// let snapshot = MarketDataSnapshot {
|
|
/// timestamp: Utc::now(),
|
|
/// symbol: "AAPL".to_owned(),
|
|
/// price: Decimal::new(15000, 2), // $150.00
|
|
/// volume: Decimal::new(1000000, 0), // 1M shares
|
|
/// };
|
|
/// ```
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MarketDataSnapshot {
|
|
/// Timestamp of the market data snapshot
|
|
pub timestamp: DateTime<Utc>,
|
|
/// Trading symbol (e.g., "AAPL", "MSFT")
|
|
pub symbol: String,
|
|
/// Current market price
|
|
pub price: Decimal,
|
|
/// Trading volume at this timestamp
|
|
pub volume: Decimal,
|
|
}
|
|
|
|
/// Feature vector for ML model input
|
|
///
|
|
/// A wrapper around a vector of f64 values that represents extracted features
|
|
/// for machine learning models. Features are numerical representations of
|
|
/// market data, indicators, and other relevant information.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// use ml::FeatureVector;
|
|
///
|
|
/// let features = FeatureVector(vec![1.0, 2.5, -0.3, 4.2]);
|
|
/// assert_eq!(features.0.len(), 4);
|
|
/// ```
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FeatureVector(pub Vec<f64>);
|
|
|
|
impl FeatureVector {
|
|
/// Get the length of the feature vector
|
|
pub fn len(&self) -> usize {
|
|
self.0.len()
|
|
}
|
|
|
|
/// Check if the feature vector is empty
|
|
pub fn is_empty(&self) -> bool {
|
|
self.0.is_empty()
|
|
}
|
|
}
|
|
|
|
/// Integer tensor for discrete ML model operations
|
|
///
|
|
/// A wrapper around a vector of i64 values used for discrete operations
|
|
/// such as classification labels, indices, and categorical data in ML models.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// use ml::IntegerTensor;
|
|
///
|
|
/// let tensor = IntegerTensor(vec![0, 1, 2, 1, 0]);
|
|
/// assert_eq!(tensor.0.len(), 5);
|
|
/// ```
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct IntegerTensor(pub Vec<i64>);
|
|
|
|
/// Summary of model update operations
|
|
///
|
|
/// Provides information about batch update operations on ML models,
|
|
/// including success counts and overall statistics. Used for monitoring
|
|
/// and logging model maintenance operations.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// use ml::UpdateSummary;
|
|
///
|
|
/// let summary = UpdateSummary {
|
|
/// updated_models: 5,
|
|
/// total_models: 10,
|
|
/// };
|
|
///
|
|
/// let success_rate = summary.updated_models as f64 / summary.total_models as f64;
|
|
/// println!("Update success rate: {:.1}%", success_rate * 100.0);
|
|
/// ```
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct UpdateSummary {
|
|
/// Number of models successfully updated
|
|
pub updated_models: usize,
|
|
/// Total number of models in the update operation
|
|
pub total_models: usize,
|
|
}
|
|
|
|
use thiserror::Error;
|
|
|
|
/// Machine Learning specific errors
|
|
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
|
|
pub enum MLError {
|
|
/// Configuration error
|
|
#[error("Configuration error: {reason}")]
|
|
ConfigError { reason: String },
|
|
|
|
/// Configuration error (alternative naming)
|
|
#[error("Configuration error: {0}")]
|
|
ConfigurationError(String),
|
|
|
|
/// Dimension mismatch error
|
|
#[error("Dimension mismatch: expected {expected}, got {actual}")]
|
|
DimensionMismatch { expected: usize, actual: usize },
|
|
|
|
/// Graph-related error
|
|
#[error("Graph error: {message}")]
|
|
GraphError { message: String },
|
|
|
|
/// Resource limit exceeded
|
|
#[error("Resource limit exceeded: {resource} limit {limit}")]
|
|
ResourceLimit { resource: String, limit: usize },
|
|
|
|
/// Serialization error
|
|
#[error("Serialization error: {reason}")]
|
|
SerializationError { reason: String },
|
|
|
|
/// Validation error
|
|
#[error("Validation error: {message}")]
|
|
ValidationError { message: String },
|
|
|
|
/// Concurrency error
|
|
#[error("Concurrency error in operation: {operation}")]
|
|
ConcurrencyError { operation: String },
|
|
|
|
/// Invalid input error
|
|
#[error("Invalid input: {0}")]
|
|
InvalidInput(String),
|
|
|
|
/// Initialization error
|
|
#[error("Initialization error in {component}: {message}")]
|
|
InitializationError { component: String, message: String },
|
|
|
|
/// Training error
|
|
#[error("Training error: {0}")]
|
|
TrainingError(String),
|
|
|
|
/// Inference error
|
|
#[error("Inference error: {0}")]
|
|
InferenceError(String),
|
|
|
|
/// Model error
|
|
#[error("Model error: {0}")]
|
|
ModelError(String),
|
|
|
|
/// Model not trained error
|
|
#[error("Model not trained: {0}")]
|
|
NotTrained(String),
|
|
|
|
/// Anyhow error wrapping
|
|
#[error("General error: {0}")]
|
|
AnyhowError(String),
|
|
|
|
/// Tensor creation error
|
|
#[error("Tensor creation error in {operation}: {reason}")]
|
|
TensorCreationError { operation: String, reason: String },
|
|
|
|
/// Tensor operation error
|
|
#[error("Tensor operation error: {0}")]
|
|
TensorOperationError(String),
|
|
|
|
/// Lock error
|
|
#[error("Lock error: {0}")]
|
|
LockError(String),
|
|
|
|
/// Model not found error
|
|
#[error("Model not found: {0}")]
|
|
ModelNotFound(String),
|
|
|
|
/// Insufficient data error
|
|
#[error("Insufficient data: {0}")]
|
|
InsufficientData(String),
|
|
|
|
/// Checkpoint error
|
|
#[error("Checkpoint error: {0}")]
|
|
CheckpointError(String),
|
|
|
|
/// Device error (GPU/CUDA unavailable)
|
|
#[error("Device error: {0}")]
|
|
DeviceError(String),
|
|
}
|
|
|
|
// Implement From trait for candle_core::Error
|
|
impl From<candle_core::Error> for MLError {
|
|
fn from(err: candle_core::Error) -> Self {
|
|
MLError::ModelError(format!("Candle error: {}", err))
|
|
}
|
|
}
|
|
|
|
// Implement From trait for LabelingError
|
|
impl From<labeling::gpu_acceleration::LabelingError> for MLError {
|
|
fn from(err: labeling::gpu_acceleration::LabelingError) -> Self {
|
|
MLError::InferenceError(err.to_string())
|
|
}
|
|
}
|
|
|
|
// Implement From trait for anyhow::Error
|
|
impl From<anyhow::Error> for MLError {
|
|
fn from(err: anyhow::Error) -> Self {
|
|
MLError::AnyhowError(err.to_string())
|
|
}
|
|
}
|
|
|
|
// Implement From trait for std::io::Error
|
|
impl From<std::io::Error> for MLError {
|
|
fn from(err: std::io::Error) -> Self {
|
|
MLError::ModelError(format!("IO error: {}", err))
|
|
}
|
|
}
|
|
|
|
// UNIFIED ERROR HANDLING: Convert all ML errors to CommonError for workspace consistency
|
|
impl From<MLError> for CommonError {
|
|
fn from(err: MLError) -> Self {
|
|
match err {
|
|
MLError::ConfigError { reason } => {
|
|
CommonError::config(format!("ML configuration error: {}", reason))
|
|
},
|
|
MLError::ConfigurationError(msg) => {
|
|
CommonError::config(format!("ML configuration error: {}", msg))
|
|
},
|
|
MLError::InitializationError { component, message } => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML initialization error in {}: {}", component, message),
|
|
),
|
|
MLError::DimensionMismatch { expected, actual } => CommonError::validation(format!(
|
|
"ML dimension mismatch: expected {}, got {}",
|
|
expected, actual
|
|
)),
|
|
MLError::GraphError { message } => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML graph error: {}", message),
|
|
),
|
|
MLError::ResourceLimit { resource, limit } => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML resource limit exceeded: {} limit {}", resource, limit),
|
|
),
|
|
MLError::SerializationError { reason } => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML serialization error: {}", reason),
|
|
),
|
|
MLError::ValidationError { message } => {
|
|
CommonError::validation(format!("ML validation error: {}", message))
|
|
},
|
|
MLError::ConcurrencyError { operation } => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML concurrency error in operation: {}", operation),
|
|
),
|
|
MLError::InvalidInput(msg) => {
|
|
CommonError::validation(format!("ML invalid input: {}", msg))
|
|
},
|
|
MLError::TrainingError(msg) => {
|
|
CommonError::service(ErrorCategory::System, format!("ML training error: {}", msg))
|
|
},
|
|
MLError::InferenceError(msg) => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML inference error: {}", msg),
|
|
),
|
|
MLError::ModelError(msg) => {
|
|
CommonError::service(ErrorCategory::System, format!("ML model error: {}", msg))
|
|
},
|
|
MLError::CheckpointError(msg) => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML checkpoint error: {}", msg),
|
|
),
|
|
MLError::NotTrained(msg) => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML model not trained: {}", msg),
|
|
),
|
|
MLError::AnyhowError(msg) => {
|
|
CommonError::service(ErrorCategory::System, format!("ML error: {}", msg))
|
|
},
|
|
MLError::TensorCreationError { operation, reason } => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML tensor creation error in {}: {}", operation, reason),
|
|
),
|
|
MLError::TensorOperationError(msg) => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML tensor operation error: {}", msg),
|
|
),
|
|
MLError::LockError(msg) => {
|
|
CommonError::service(ErrorCategory::System, format!("ML lock error: {}", msg))
|
|
},
|
|
MLError::ModelNotFound(msg) => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML model not found: {}", msg),
|
|
),
|
|
MLError::InsufficientData(msg) => {
|
|
CommonError::validation(format!("ML insufficient data: {}", msg))
|
|
},
|
|
MLError::DeviceError(msg) => CommonError::service(
|
|
ErrorCategory::System,
|
|
format!("ML device error: {}", msg),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert common type errors to MLError
|
|
impl From<CommonTypeError> for MLError {
|
|
fn from(err: CommonTypeError) -> Self {
|
|
MLError::ModelError(format!("Common type error: {}", err))
|
|
}
|
|
}
|
|
|
|
impl From<serde_json::Error> for MLError {
|
|
fn from(err: serde_json::Error) -> Self {
|
|
MLError::SerializationError {
|
|
reason: err.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<inference::RealInferenceError> for MLError {
|
|
fn from(err: inference::RealInferenceError) -> Self {
|
|
match err {
|
|
inference::RealInferenceError::GpuRequired { reason } => {
|
|
MLError::ModelError(format!("GPU required: {}", reason))
|
|
},
|
|
inference::RealInferenceError::ComputationFailed { reason } => {
|
|
MLError::InferenceError(reason)
|
|
},
|
|
inference::RealInferenceError::FeatureMismatch { expected, actual } => {
|
|
MLError::DimensionMismatch { expected, actual }
|
|
},
|
|
inference::RealInferenceError::PredictionValidation { reason } => {
|
|
MLError::ValidationError { message: reason }
|
|
},
|
|
inference::RealInferenceError::HardwareError { reason } => {
|
|
MLError::ModelError(format!("Hardware error: {}", reason))
|
|
},
|
|
other @ inference::RealInferenceError::ModelNotLoaded { .. }
|
|
| other @ inference::RealInferenceError::ArchitectureError { .. }
|
|
| other @ inference::RealInferenceError::TimeoutExceeded { .. }
|
|
| other @ inference::RealInferenceError::ModelDrift { .. } => {
|
|
MLError::InferenceError(other.to_string())
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
// Implement From<ProductionTrainingError> for MLError
|
|
impl From<training_pipeline::ProductionTrainingError> for MLError {
|
|
fn from(err: training_pipeline::ProductionTrainingError) -> Self {
|
|
match err {
|
|
training_pipeline::ProductionTrainingError::ConfigError { reason } => {
|
|
MLError::ConfigError { reason }
|
|
},
|
|
training_pipeline::ProductionTrainingError::ArchitectureError { reason } => {
|
|
MLError::ModelError(format!("Architecture error: {}", reason))
|
|
},
|
|
training_pipeline::ProductionTrainingError::DataError { reason } => {
|
|
MLError::ValidationError {
|
|
message: format!("Data error: {}", reason),
|
|
}
|
|
},
|
|
training_pipeline::ProductionTrainingError::OptimizationError { reason } => {
|
|
MLError::TrainingError(format!("Optimization error: {}", reason))
|
|
},
|
|
training_pipeline::ProductionTrainingError::FinancialError { reason } => {
|
|
MLError::ValidationError {
|
|
message: format!("Financial error: {}", reason),
|
|
}
|
|
},
|
|
training_pipeline::ProductionTrainingError::SafetyViolation { reason } => {
|
|
MLError::ValidationError {
|
|
message: format!("Safety violation: {}", reason),
|
|
}
|
|
},
|
|
training_pipeline::ProductionTrainingError::ConvergenceError { reason } => {
|
|
MLError::TrainingError(format!("Convergence error: {}", reason))
|
|
},
|
|
training_pipeline::ProductionTrainingError::ResourceError { reason } => {
|
|
MLError::ModelError(format!("Resource error: {}", reason))
|
|
},
|
|
training_pipeline::ProductionTrainingError::GpuRequired { reason } => {
|
|
MLError::ModelError(format!("GPU required: {}", reason))
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note: From trait for liquid::LiquidError is implemented in the liquid module to avoid conflicts
|
|
|
|
/// Result type for ML operations
|
|
pub type MLResult<T> = Result<T, MLError>;
|
|
|
|
/// New unified result type using CommonError for better integration
|
|
pub type UnifiedMLResult<T> = Result<T, CommonError>;
|
|
|
|
/// Precision factor for fixed-point arithmetic
|
|
pub const PRECISION_FACTOR: i64 = 100_000_000;
|
|
|
|
/// Maximum inference latency target in microseconds
|
|
pub const MAX_INFERENCE_LATENCY_US: u64 = 100;
|
|
|
|
// ========== CORE ML MODULES ==========
|
|
// Core ML modules
|
|
pub mod backtesting; // Backtesting framework for barrier optimization
|
|
pub mod checkpoint;
|
|
pub mod config; // Configuration module for feature extraction
|
|
pub mod cuda_compat; // CUDA-compatible operations (manual sigmoid, etc.)
|
|
pub mod data_loaders; // Data loaders for ML training
|
|
pub mod dqn;
|
|
pub mod gradient_accumulation; // Gradient accumulation for mini-batch training
|
|
pub mod gradient_utils; // Gradient utilities (clipping, monitoring)
|
|
pub mod gpu;
|
|
pub mod diffusion;
|
|
pub mod ensemble;
|
|
pub mod evaluation; // DQN evaluation engine (backtest metrics, Sharpe ratio)
|
|
pub mod flash_attention;
|
|
pub mod hyperopt; // Bayesian hyperparameter optimization (egobox)
|
|
pub mod integration;
|
|
pub mod kan;
|
|
pub mod labeling;
|
|
pub mod liquid;
|
|
pub mod mamba;
|
|
pub mod memory_optimization; // Memory optimization utilities (lazy loading, quantization, precision)
|
|
pub mod microstructure;
|
|
pub mod optimizers;
|
|
pub mod paper_trading;
|
|
pub mod ppo;
|
|
pub mod preprocessing; // Data preprocessing (log returns, normalization, outlier clipping)
|
|
pub mod risk;
|
|
pub mod safety;
|
|
pub mod security; // ML security (prediction validation, anomaly detection)
|
|
pub mod tft;
|
|
pub mod tgnn;
|
|
pub mod tlob;
|
|
pub mod xlstm;
|
|
|
|
// Re-export quantized TFT types (Wave 9.12)
|
|
pub use tft::{
|
|
QuantizedGatedResidualNetwork, QuantizedLSTMEncoder, QuantizedTemporalAttention,
|
|
QuantizedTemporalFusionTransformer, QuantizedVariableSelectionNetwork,
|
|
};
|
|
pub mod trainers; // ML model trainers with gRPC integration
|
|
pub mod types; // Canonical shared types (OHLCVBar, etc.)
|
|
pub mod transformers;
|
|
pub mod universe;
|
|
|
|
// ============================================================================
|
|
// MANDATORY CUDA TRAINING DEVICE
|
|
// ============================================================================
|
|
//
|
|
// ALL ML training requires CUDA GPU acceleration. CPU fallback wastes time.
|
|
//
|
|
// This module provides a fail-fast device initialization function that:
|
|
// 1. Requires CUDA GPU (no silent CPU fallback)
|
|
// 2. Provides helpful error messages for common setup issues
|
|
// 3. Ensures consistent device initialization across all trainers
|
|
//
|
|
// Training scripts MUST use this function instead of Device::cuda_if_available()
|
|
|
|
/// Get mandatory CUDA device for training
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns `MLError::DeviceError` if CUDA GPU is not available
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```no_run
|
|
/// use ml::get_training_device;
|
|
///
|
|
/// let device = get_training_device().expect("GPU required");
|
|
/// ```
|
|
pub fn get_training_device() -> Result<candle_core::Device, MLError> {
|
|
match candle_core::Device::new_cuda(0) {
|
|
Ok(device) => Ok(device),
|
|
Err(e) => {
|
|
tracing::error!(
|
|
"CUDA GPU not available: {}. \
|
|
Troubleshooting: (1) nvidia-smi, (2) nvcc --version, \
|
|
(3) check LD_LIBRARY_PATH, (4) cargo build --features cuda, \
|
|
(5) check CUDA_HOME",
|
|
e
|
|
);
|
|
Err(MLError::DeviceError(format!(
|
|
"CUDA GPU required but unavailable: {}",
|
|
e
|
|
)))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get CUDA device with index (for multi-GPU setups)
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns `MLError::DeviceError` if CUDA GPU at specified index is not available
|
|
pub fn get_training_device_at(device_id: usize) -> Result<candle_core::Device, MLError> {
|
|
match candle_core::Device::new_cuda(device_id) {
|
|
Ok(device) => Ok(device),
|
|
Err(e) => {
|
|
tracing::error!(
|
|
"CUDA GPU {} not available: {}. Check available GPUs with: nvidia-smi",
|
|
device_id,
|
|
e
|
|
);
|
|
Err(MLError::DeviceError(format!(
|
|
"CUDA GPU {} required but unavailable: {}",
|
|
device_id, e
|
|
)))
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========== INFRASTRUCTURE MODULES ==========
|
|
// Infrastructure
|
|
pub mod benchmark;
|
|
pub mod benchmarks;
|
|
pub mod common;
|
|
pub mod metrics; // Performance metrics (Sharpe ratio, etc.)
|
|
pub mod training;
|
|
|
|
// ========== MODEL DEPLOYMENT AND FACTORY ==========
|
|
pub mod model_factory;
|
|
|
|
// ========== CORE EXPORTS ==========
|
|
// Core exports
|
|
pub mod error;
|
|
pub mod features; // Feature cache and extraction (Parquet + MinIO)
|
|
pub mod feature_cache; // MBP-10 OFI feature caching for hyperopt speedup
|
|
|
|
pub mod inference;
|
|
pub mod model;
|
|
pub mod performance;
|
|
pub mod production;
|
|
pub mod validation;
|
|
|
|
// ========== ADDITIONAL MODULES ==========
|
|
// Additional ML processing modules
|
|
pub mod batch_processing; // Batch processing for ML operations
|
|
pub mod bridge; // Type system bridge for ML-Financial integration
|
|
pub mod portfolio_transformer; // Portfolio-specific transformer
|
|
pub mod regime; // Wave D: Structural breaks and regime classification
|
|
pub mod regime_detection; // Market regime detection
|
|
pub mod tensor_ops;
|
|
pub mod examples;
|
|
pub mod models_demo;
|
|
pub mod observability;
|
|
pub mod qat_metrics_exporter; // QAT Prometheus metrics export
|
|
pub mod stress_testing; // Stress testing framework
|
|
pub mod training_pipeline; // Complete training pipeline system
|
|
pub mod traits; // Common traits for ML models // Production observability and monitoring // Integration with model_loader crate
|
|
|
|
pub mod real_data_loader;
|
|
pub mod walk_forward;
|
|
pub mod data_validation;
|
|
pub mod random_model;
|
|
pub mod model_registry;
|
|
pub mod data_pipeline;
|
|
pub mod asset_selection;
|
|
pub mod registry; // Operational maturity: model lifecycle (Candidate → Staging → Production → Archived)
|
|
|
|
// ========== MISSING TYPES STUBS ==========
|
|
|
|
/// Application result wrapper for ML operations
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MLAppResult<T> {
|
|
pub data: T,
|
|
pub success: bool,
|
|
pub message: Option<String>,
|
|
pub execution_time_ms: u64,
|
|
pub metadata: HashMap<String, String>,
|
|
}
|
|
|
|
impl<T> MLAppResult<T> {
|
|
/// Create a successful result
|
|
pub fn success(data: T) -> Self {
|
|
Self {
|
|
data,
|
|
success: true,
|
|
message: None,
|
|
execution_time_ms: 0,
|
|
metadata: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Create a failed result with message
|
|
pub fn error(data: T, message: String) -> Self {
|
|
Self {
|
|
data,
|
|
success: false,
|
|
message: Some(message),
|
|
execution_time_ms: 0,
|
|
metadata: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Set execution time
|
|
pub fn with_timing(mut self, execution_time_ms: u64) -> Self {
|
|
self.execution_time_ms = execution_time_ms;
|
|
self
|
|
}
|
|
|
|
/// Add metadata
|
|
pub fn with_metadata(mut self, key: String, value: String) -> Self {
|
|
self.metadata.insert(key, value);
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Performance profile configuration for HFT models
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct HFTPerformanceProfile {
|
|
pub max_latency_us: u64,
|
|
pub target_throughput: u32,
|
|
pub memory_limit_mb: u64,
|
|
pub cpu_affinity: Option<Vec<usize>>,
|
|
pub gpu_enabled: bool,
|
|
pub batch_size: u32,
|
|
pub optimization_level: OptimizationLevel,
|
|
}
|
|
|
|
/// Optimization levels for HFT performance
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
pub enum OptimizationLevel {
|
|
/// Maximum speed, minimal safety checks
|
|
UltraLow,
|
|
/// Balanced speed and safety
|
|
Low,
|
|
/// Standard optimization
|
|
Medium,
|
|
/// Conservative with full validation
|
|
High,
|
|
}
|
|
|
|
impl Default for HFTPerformanceProfile {
|
|
fn default() -> Self {
|
|
Self {
|
|
max_latency_us: 100, // 100 microseconds target
|
|
target_throughput: 10000, // 10k operations per second
|
|
memory_limit_mb: 1024, // 1GB memory limit
|
|
cpu_affinity: None,
|
|
gpu_enabled: false,
|
|
batch_size: 1,
|
|
optimization_level: OptimizationLevel::Medium,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create HFT performance profile with default settings
|
|
pub fn create_hft_performance_profile() -> HFTPerformanceProfile {
|
|
HFTPerformanceProfile::default()
|
|
}
|
|
|
|
/// Create HFT performance profile with custom latency target
|
|
pub fn create_hft_performance_profile_with_latency(max_latency_us: u64) -> HFTPerformanceProfile {
|
|
HFTPerformanceProfile {
|
|
max_latency_us,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create HFT performance profile optimized for ultra-low latency
|
|
pub fn create_ultra_low_latency_profile() -> HFTPerformanceProfile {
|
|
HFTPerformanceProfile {
|
|
max_latency_us: 10, // 10 microseconds target
|
|
target_throughput: 50000, // 50k operations per second
|
|
memory_limit_mb: 512, // Reduced memory for cache efficiency
|
|
gpu_enabled: true, // Enable GPU acceleration
|
|
batch_size: 1, // No batching for minimal latency
|
|
optimization_level: OptimizationLevel::UltraLow,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
// ========== UNIFIED ML MODEL INTERFACE ==========
|
|
|
|
use async_trait::async_trait;
|
|
use chrono::{DateTime, Utc};
|
|
use futures::future::join_all;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use tokio::sync::RwLock;
|
|
|
|
/// Features vector for ML model input
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Features {
|
|
/// Raw feature values
|
|
pub values: Vec<f64>,
|
|
/// Feature names for debugging
|
|
pub names: Vec<String>,
|
|
/// Timestamp of features
|
|
pub timestamp: u64,
|
|
/// Symbol these features are for
|
|
pub symbol: Option<String>,
|
|
}
|
|
|
|
impl Features {
|
|
pub fn new(values: Vec<f64>, names: Vec<String>) -> Self {
|
|
Self {
|
|
values,
|
|
names,
|
|
timestamp: std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_micros() as u64,
|
|
symbol: None,
|
|
}
|
|
}
|
|
|
|
/// Set the symbol for this market data point
|
|
///
|
|
/// # Arguments
|
|
/// * `symbol` - The trading symbol (e.g., "AAPL", "MSFT")
|
|
///
|
|
/// # Returns
|
|
/// Modified MarketData instance with symbol set
|
|
pub fn with_symbol(mut self, symbol: String) -> Self {
|
|
self.symbol = Some(symbol);
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Model prediction result
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ModelPrediction {
|
|
/// Predicted value (price direction, probability, etc.)
|
|
pub value: f64,
|
|
/// Model confidence (0.0 to 1.0)
|
|
pub confidence: f64,
|
|
/// Additional model-specific metadata
|
|
pub metadata: HashMap<String, serde_json::Value>,
|
|
/// Prediction timestamp
|
|
pub timestamp: u64,
|
|
/// Model identifier
|
|
pub model_id: String,
|
|
}
|
|
|
|
impl ModelPrediction {
|
|
pub fn new(model_id: String, value: f64, confidence: f64) -> Self {
|
|
Self {
|
|
value,
|
|
confidence,
|
|
metadata: HashMap::new(),
|
|
timestamp: std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_micros() as u64,
|
|
model_id,
|
|
}
|
|
}
|
|
|
|
/// Add metadata to the model prediction
|
|
///
|
|
/// # Arguments
|
|
/// * `key` - Metadata key identifier
|
|
/// * `value` - JSON value containing metadata
|
|
///
|
|
/// # Returns
|
|
/// Modified ModelPrediction with additional metadata
|
|
pub fn with_metadata(mut self, key: String, value: serde_json::Value) -> Self {
|
|
self.metadata.insert(key, value);
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Feedback for model weight updates
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Feedback {
|
|
/// Actual outcome (for supervised learning)
|
|
pub actual_value: Option<f64>,
|
|
/// Reward signal (for reinforcement learning)
|
|
pub reward: Option<f64>,
|
|
/// Trading performance metrics
|
|
pub performance_metrics: HashMap<String, f64>,
|
|
/// Timestamp of feedback
|
|
pub timestamp: u64,
|
|
}
|
|
|
|
impl Feedback {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
actual_value: None,
|
|
reward: None,
|
|
performance_metrics: HashMap::new(),
|
|
timestamp: std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_micros() as u64,
|
|
}
|
|
}
|
|
|
|
/// Set the actual outcome value for supervised learning feedback
|
|
///
|
|
/// # Arguments
|
|
/// * `actual` - The actual observed value
|
|
///
|
|
/// # Returns
|
|
/// Modified Feedback with actual value set
|
|
pub fn with_actual(mut self, actual: f64) -> Self {
|
|
self.actual_value = Some(actual);
|
|
self
|
|
}
|
|
|
|
/// Set the reward signal for reinforcement learning feedback
|
|
///
|
|
/// # Arguments
|
|
/// * `reward` - The reward value (positive for good outcomes, negative for bad)
|
|
///
|
|
/// # Returns
|
|
/// Modified Feedback with reward signal set
|
|
pub fn with_reward(mut self, reward: f64) -> Self {
|
|
self.reward = Some(reward);
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Unified interface for all ML models in the system
|
|
#[async_trait]
|
|
pub trait MLModel: Send + Sync + std::fmt::Debug {
|
|
/// Get unique model identifier
|
|
fn name(&self) -> &str;
|
|
|
|
/// Get model type
|
|
fn model_type(&self) -> ModelType;
|
|
|
|
/// Make prediction based on features
|
|
async fn predict(&self, features: &Features) -> MLResult<ModelPrediction>;
|
|
|
|
/// Get current model confidence score (0.0 to 1.0)
|
|
fn get_confidence(&self) -> f64;
|
|
|
|
/// Update model weights based on feedback (optional - not all models support online learning)
|
|
async fn update_weights(&mut self, _feedback: &Feedback) -> MLResult<()> {
|
|
// Default implementation does nothing (for immutable models)
|
|
Ok(())
|
|
}
|
|
|
|
/// Check if model is ready for predictions
|
|
fn is_ready(&self) -> bool {
|
|
true // Default to ready
|
|
}
|
|
|
|
/// Get model metadata
|
|
fn get_metadata(&self) -> ModelMetadata;
|
|
|
|
/// Validate input features
|
|
fn validate_features(&self, features: &Features) -> MLResult<()> {
|
|
// Default validation - check for empty features
|
|
if features.values.is_empty() {
|
|
return Err(MLError::ValidationError {
|
|
message: "Empty feature vector".to_owned(),
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Thread-safe model registry using DashMap for high-performance concurrent access
|
|
pub struct ModelRegistry {
|
|
/// Models stored by name
|
|
models: dashmap::DashMap<String, Arc<dyn MLModel>>,
|
|
/// Registry metadata
|
|
metadata: Arc<RwLock<RegistryMetadata>>,
|
|
}
|
|
|
|
impl std::fmt::Debug for ModelRegistry {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("ModelRegistry")
|
|
.field(
|
|
"models",
|
|
&format_args!("<DashMap with {} models>", self.models.len()),
|
|
)
|
|
.field("metadata", &self.metadata)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct RegistryMetadata {
|
|
created_at: std::time::SystemTime,
|
|
total_registrations: u64,
|
|
last_access: std::time::SystemTime,
|
|
}
|
|
|
|
impl ModelRegistry {
|
|
/// Create new model registry
|
|
pub fn new() -> Self {
|
|
Self {
|
|
models: dashmap::DashMap::new(),
|
|
metadata: Arc::new(RwLock::new(RegistryMetadata {
|
|
created_at: std::time::SystemTime::now(),
|
|
total_registrations: 0,
|
|
last_access: std::time::SystemTime::now(),
|
|
})),
|
|
}
|
|
}
|
|
|
|
/// Register a model in the registry
|
|
pub async fn register(&self, model: Arc<dyn MLModel>) -> MLResult<()> {
|
|
let name = model.name().to_string();
|
|
|
|
// Check if model is ready
|
|
if !model.is_ready() {
|
|
return Err(MLError::ModelError(format!("Model {} is not ready", name)));
|
|
}
|
|
|
|
self.models.insert(name.clone(), model);
|
|
|
|
// Update metadata
|
|
{
|
|
let mut meta = self.metadata.write().await;
|
|
meta.total_registrations += 1;
|
|
meta.last_access = std::time::SystemTime::now();
|
|
}
|
|
|
|
tracing::info!("Registered ML model: {}", name);
|
|
Ok(())
|
|
}
|
|
|
|
/// Get model by name
|
|
pub async fn get(&self, name: &str) -> Option<Arc<dyn MLModel>> {
|
|
// Update last access time
|
|
{
|
|
let mut meta = self.metadata.write().await;
|
|
meta.last_access = std::time::SystemTime::now();
|
|
}
|
|
|
|
self.models.get(name).map(|entry| entry.value().clone())
|
|
}
|
|
|
|
/// Get all registered models
|
|
pub fn get_all(&self) -> Vec<Arc<dyn MLModel>> {
|
|
self.models
|
|
.iter()
|
|
.map(|entry| entry.value().clone())
|
|
.collect()
|
|
}
|
|
|
|
/// Get model names
|
|
pub fn get_model_names(&self) -> Vec<String> {
|
|
self.models
|
|
.iter()
|
|
.map(|entry| entry.key().clone())
|
|
.collect()
|
|
}
|
|
|
|
/// Remove model from registry
|
|
pub async fn remove(&self, name: &str) -> Option<Arc<dyn MLModel>> {
|
|
let result = self.models.remove(name).map(|(_, model)| model);
|
|
|
|
if result.is_some() {
|
|
tracing::info!("Removed ML model: {}", name);
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
/// Get registry statistics
|
|
pub async fn get_stats(&self) -> RegistryStats {
|
|
let meta = self.metadata.read().await;
|
|
RegistryStats {
|
|
total_models: self.models.len(),
|
|
total_registrations: meta.total_registrations,
|
|
created_at: meta.created_at,
|
|
last_access: meta.last_access,
|
|
}
|
|
}
|
|
|
|
/// Parallel prediction across all models
|
|
pub async fn predict_all(&self, features: &Features) -> Vec<MLResult<ModelPrediction>> {
|
|
let models = self.get_all();
|
|
let futures = models.iter().map(|model| {
|
|
let features = features.clone();
|
|
async move { model.predict(&features).await }
|
|
});
|
|
|
|
join_all(futures).await
|
|
}
|
|
|
|
/// Parallel prediction across specific models
|
|
pub async fn predict_selected(
|
|
&self,
|
|
model_names: &[String],
|
|
features: &Features,
|
|
) -> Vec<MLResult<ModelPrediction>> {
|
|
let futures = model_names.iter().map(|name| {
|
|
let name = name.clone();
|
|
let features = features.clone();
|
|
async move {
|
|
if let Some(model) = self.get(&name).await {
|
|
model.predict(&features).await
|
|
} else {
|
|
Err(MLError::ModelNotFound(name))
|
|
}
|
|
}
|
|
});
|
|
|
|
join_all(futures).await
|
|
}
|
|
}
|
|
|
|
impl Default for ModelRegistry {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// Registry statistics
|
|
#[derive(Debug, Clone)]
|
|
pub struct RegistryStats {
|
|
pub total_models: usize,
|
|
pub total_registrations: u64,
|
|
pub created_at: std::time::SystemTime,
|
|
pub last_access: std::time::SystemTime,
|
|
}
|
|
|
|
/// Global model registry instance (singleton pattern)
|
|
static GLOBAL_REGISTRY: once_cell::sync::Lazy<Arc<ModelRegistry>> =
|
|
once_cell::sync::Lazy::new(|| Arc::new(ModelRegistry::new()));
|
|
|
|
/// Get global model registry
|
|
pub fn get_global_registry() -> Arc<ModelRegistry> {
|
|
GLOBAL_REGISTRY.clone()
|
|
}
|
|
|
|
// ========== PARALLEL EXECUTION OPTIMIZATIONS ==========
|
|
|
|
/// High-performance parallel executor for ML models optimized for sub-50μs latency
|
|
#[derive(Debug)]
|
|
pub struct ParallelExecutor {
|
|
/// Performance profile
|
|
profile: HFTPerformanceProfile,
|
|
/// CPU affinity settings
|
|
cpu_affinity: Option<Vec<usize>>,
|
|
/// Thread pool for CPU-bound operations
|
|
cpu_pool: Arc<rayon::ThreadPool>,
|
|
/// Async runtime handle
|
|
runtime_handle: tokio::runtime::Handle,
|
|
}
|
|
|
|
impl ParallelExecutor {
|
|
/// Create new parallel executor with HFT performance profile
|
|
pub fn new(profile: HFTPerformanceProfile) -> Result<Self, MLError> {
|
|
// Create dedicated thread pool based on profile
|
|
let cpu_pool = rayon::ThreadPoolBuilder::new()
|
|
.num_threads(
|
|
profile
|
|
.cpu_affinity
|
|
.as_ref()
|
|
.map(|v| v.len())
|
|
.unwrap_or(num_cpus::get()),
|
|
)
|
|
.thread_name(|i| format!("ml-cpu-{}", i))
|
|
.build()
|
|
.map_err(|e| MLError::ModelError(format!("Failed to create thread pool: {}", e)))?;
|
|
|
|
let runtime_handle = tokio::runtime::Handle::try_current()
|
|
.map_err(|e| MLError::ModelError(format!("No tokio runtime available: {}", e)))?;
|
|
|
|
let cpu_affinity = profile.cpu_affinity.clone();
|
|
|
|
Ok(Self {
|
|
profile,
|
|
cpu_affinity,
|
|
cpu_pool: Arc::new(cpu_pool),
|
|
runtime_handle,
|
|
})
|
|
}
|
|
|
|
/// Execute parallel predictions with latency optimization
|
|
pub async fn execute_parallel_predictions(
|
|
&self,
|
|
models: Vec<Arc<dyn MLModel>>,
|
|
features: Features,
|
|
) -> Vec<MLResult<ModelPrediction>> {
|
|
let start_time = std::time::Instant::now();
|
|
|
|
// Determine execution strategy based on performance profile
|
|
let results = match self.profile.optimization_level {
|
|
OptimizationLevel::UltraLow => {
|
|
// Ultra-low latency: parallel execution with minimal overhead
|
|
self.execute_ultra_low_latency(models, features).await
|
|
},
|
|
OptimizationLevel::Low => {
|
|
// Low latency: parallel with basic batching
|
|
self.execute_low_latency(models, features).await
|
|
},
|
|
OptimizationLevel::Medium => {
|
|
// Medium: balanced parallel execution
|
|
self.execute_balanced(models, features).await
|
|
},
|
|
OptimizationLevel::High => {
|
|
// High: conservative with full validation
|
|
self.execute_conservative(models, features).await
|
|
},
|
|
};
|
|
|
|
let execution_time = start_time.elapsed();
|
|
|
|
// Log performance if exceeding target latency
|
|
if execution_time.as_micros() > self.profile.max_latency_us as u128 {
|
|
tracing::warn!(
|
|
"Parallel execution exceeded target latency: {}μs > {}μs",
|
|
execution_time.as_micros(),
|
|
self.profile.max_latency_us
|
|
);
|
|
}
|
|
|
|
results
|
|
}
|
|
|
|
/// Ultra-low latency execution (<10μs target)
|
|
async fn execute_ultra_low_latency(
|
|
&self,
|
|
models: Vec<Arc<dyn MLModel>>,
|
|
features: Features,
|
|
) -> Vec<MLResult<ModelPrediction>> {
|
|
// Use futures::future::join_all for minimal overhead
|
|
let futures = models.into_iter().map(|model| {
|
|
let features = features.clone();
|
|
async move { model.predict(&features).await }
|
|
});
|
|
|
|
join_all(futures).await
|
|
}
|
|
|
|
/// Low latency execution with basic optimizations
|
|
async fn execute_low_latency(
|
|
&self,
|
|
models: Vec<Arc<dyn MLModel>>,
|
|
features: Features,
|
|
) -> Vec<MLResult<ModelPrediction>> {
|
|
// Group models by type for potential batching
|
|
let mut model_groups: HashMap<ModelType, Vec<Arc<dyn MLModel>>> = HashMap::new();
|
|
|
|
for model in models {
|
|
let model_type = model.model_type();
|
|
model_groups.entry(model_type).or_default().push(model);
|
|
}
|
|
|
|
let mut all_futures = Vec::new();
|
|
|
|
for (_, group_models) in model_groups {
|
|
for model in group_models {
|
|
let features = features.clone();
|
|
all_futures.push(async move { model.predict(&features).await });
|
|
}
|
|
}
|
|
|
|
join_all(all_futures).await
|
|
}
|
|
|
|
/// Balanced execution with moderate optimizations
|
|
async fn execute_balanced(
|
|
&self,
|
|
models: Vec<Arc<dyn MLModel>>,
|
|
features: Features,
|
|
) -> Vec<MLResult<ModelPrediction>> {
|
|
// Validate features once for all models
|
|
for model in &models {
|
|
if let Err(e) = model.validate_features(&features) {
|
|
tracing::debug!(
|
|
"Feature validation failed for model {}: {}",
|
|
model.name(),
|
|
e
|
|
);
|
|
}
|
|
}
|
|
|
|
let futures = models.into_iter().map(|model| {
|
|
let features = features.clone();
|
|
async move {
|
|
if model.is_ready() {
|
|
model.predict(&features).await
|
|
} else {
|
|
Err(MLError::ModelError(format!(
|
|
"Model {} not ready",
|
|
model.name()
|
|
)))
|
|
}
|
|
}
|
|
});
|
|
|
|
join_all(futures).await
|
|
}
|
|
|
|
/// Conservative execution with full validation
|
|
async fn execute_conservative(
|
|
&self,
|
|
models: Vec<Arc<dyn MLModel>>,
|
|
features: Features,
|
|
) -> Vec<MLResult<ModelPrediction>> {
|
|
let mut results = Vec::new();
|
|
|
|
for model in models {
|
|
// Comprehensive validation
|
|
if !model.is_ready() {
|
|
results.push(Err(MLError::ModelError(format!(
|
|
"Model {} not ready",
|
|
model.name()
|
|
))));
|
|
continue;
|
|
}
|
|
|
|
if let Err(e) = model.validate_features(&features) {
|
|
results.push(Err(e));
|
|
continue;
|
|
}
|
|
|
|
// Execute with timeout
|
|
let prediction_future = model.predict(&features);
|
|
let timeout_duration = std::time::Duration::from_micros(self.profile.max_latency_us);
|
|
|
|
match tokio::time::timeout(timeout_duration, prediction_future).await {
|
|
Ok(result) => results.push(result),
|
|
Err(_) => results.push(Err(MLError::ModelError(format!(
|
|
"Model {} prediction timed out after {}μs",
|
|
model.name(),
|
|
self.profile.max_latency_us
|
|
)))),
|
|
}
|
|
}
|
|
|
|
results
|
|
}
|
|
|
|
/// Get execution statistics
|
|
pub fn get_stats(&self) -> ExecutorStats {
|
|
ExecutorStats {
|
|
optimization_level: self.profile.optimization_level,
|
|
target_latency_us: self.profile.max_latency_us,
|
|
cpu_threads: self.cpu_pool.current_num_threads(),
|
|
cpu_affinity: self.cpu_affinity.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Executor performance statistics
|
|
#[derive(Debug, Clone)]
|
|
pub struct ExecutorStats {
|
|
pub optimization_level: OptimizationLevel,
|
|
pub target_latency_us: u64,
|
|
pub cpu_threads: usize,
|
|
pub cpu_affinity: Option<Vec<usize>>,
|
|
}
|
|
|
|
/// Latency optimizer for ML inference pipelines
|
|
#[derive(Debug)]
|
|
pub struct LatencyOptimizer {
|
|
/// Target latency in microseconds
|
|
target_latency_us: u64,
|
|
/// Performance history
|
|
performance_history: Arc<RwLock<Vec<PerformancePoint>>>,
|
|
/// Optimization parameters
|
|
optimization_params: OptimizationParams,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct PerformancePoint {
|
|
timestamp: std::time::Instant,
|
|
latency_us: u64,
|
|
model_count: usize,
|
|
batch_size: u32,
|
|
success: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct OptimizationParams {
|
|
max_batch_size: u32,
|
|
adaptive_batching: bool,
|
|
prefetch_enabled: bool,
|
|
cache_predictions: bool,
|
|
}
|
|
|
|
impl Default for OptimizationParams {
|
|
fn default() -> Self {
|
|
Self {
|
|
max_batch_size: 8,
|
|
adaptive_batching: true,
|
|
prefetch_enabled: true,
|
|
cache_predictions: false, // Disabled for real-time trading
|
|
}
|
|
}
|
|
}
|
|
|
|
impl LatencyOptimizer {
|
|
/// Create new latency optimizer
|
|
pub fn new(target_latency_us: u64) -> Self {
|
|
Self {
|
|
target_latency_us,
|
|
performance_history: Arc::new(RwLock::new(Vec::new())),
|
|
optimization_params: OptimizationParams::default(),
|
|
}
|
|
}
|
|
|
|
/// Record performance measurement
|
|
pub async fn record_performance(
|
|
&self,
|
|
latency_us: u64,
|
|
model_count: usize,
|
|
batch_size: u32,
|
|
success: bool,
|
|
) {
|
|
let point = PerformancePoint {
|
|
timestamp: std::time::Instant::now(),
|
|
latency_us,
|
|
model_count,
|
|
batch_size,
|
|
success,
|
|
};
|
|
|
|
{
|
|
let mut history = self.performance_history.write().await;
|
|
history.push(point);
|
|
|
|
// Keep only recent history (last 1000 measurements)
|
|
if history.len() > 1000 {
|
|
let excess = history.len() - 1000;
|
|
history.drain(0..excess);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get optimization recommendations
|
|
pub async fn get_recommendations(&self) -> OptimizationRecommendations {
|
|
let history = self.performance_history.read().await;
|
|
|
|
if history.is_empty() {
|
|
return OptimizationRecommendations::default();
|
|
}
|
|
|
|
let recent_points: Vec<&PerformancePoint> = history.iter().rev().take(100).collect();
|
|
|
|
let avg_latency =
|
|
recent_points.iter().map(|p| p.latency_us).sum::<u64>() / recent_points.len() as u64;
|
|
|
|
let success_rate =
|
|
recent_points.iter().filter(|p| p.success).count() as f64 / recent_points.len() as f64;
|
|
|
|
OptimizationRecommendations {
|
|
current_avg_latency_us: avg_latency,
|
|
target_latency_us: self.target_latency_us,
|
|
success_rate,
|
|
meets_target: avg_latency <= self.target_latency_us,
|
|
recommended_batch_size: self.calculate_optimal_batch_size(&recent_points),
|
|
recommended_model_limit: self.calculate_optimal_model_limit(&recent_points),
|
|
}
|
|
}
|
|
|
|
fn calculate_optimal_batch_size(&self, points: &[&PerformancePoint]) -> u32 {
|
|
// Simple heuristic: find batch size with best latency/success ratio
|
|
let mut batch_performance: HashMap<u32, (u64, f64)> = HashMap::new();
|
|
|
|
for point in points {
|
|
let entry = batch_performance
|
|
.entry(point.batch_size)
|
|
.or_insert((0, 0.0));
|
|
entry.0 += point.latency_us;
|
|
entry.1 += if point.success { 1.0 } else { 0.0 };
|
|
}
|
|
|
|
batch_performance
|
|
.into_iter()
|
|
.filter(|(_, (_, success_count))| *success_count > 0.0)
|
|
.min_by_key(|(_, (latency, success_count))| {
|
|
// Optimize for latency with success rate weighting
|
|
((*latency as f64) / success_count) as u64
|
|
})
|
|
.map(|(batch_size, _)| batch_size)
|
|
.unwrap_or(1)
|
|
}
|
|
|
|
fn calculate_optimal_model_limit(&self, points: &[&PerformancePoint]) -> usize {
|
|
// Find the sweet spot where adding more models doesn't improve latency
|
|
let mut model_performance: HashMap<usize, u64> = HashMap::new();
|
|
|
|
for point in points {
|
|
if point.success {
|
|
let entry = model_performance.entry(point.model_count).or_insert(0);
|
|
*entry += point.latency_us;
|
|
}
|
|
}
|
|
|
|
model_performance
|
|
.into_iter()
|
|
.filter(|(_, avg_latency)| *avg_latency <= self.target_latency_us)
|
|
.max_by_key(|(model_count, _)| *model_count)
|
|
.map(|(model_count, _)| model_count)
|
|
.unwrap_or(1)
|
|
}
|
|
}
|
|
|
|
/// Optimization recommendations from latency analysis
|
|
#[derive(Debug, Clone)]
|
|
pub struct OptimizationRecommendations {
|
|
pub current_avg_latency_us: u64,
|
|
pub target_latency_us: u64,
|
|
pub success_rate: f64,
|
|
pub meets_target: bool,
|
|
pub recommended_batch_size: u32,
|
|
pub recommended_model_limit: usize,
|
|
}
|
|
|
|
impl Default for OptimizationRecommendations {
|
|
fn default() -> Self {
|
|
Self {
|
|
current_avg_latency_us: 0,
|
|
target_latency_us: 50,
|
|
success_rate: 0.0,
|
|
meets_target: false,
|
|
recommended_batch_size: 1,
|
|
recommended_model_limit: 1,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create optimized parallel executor for HFT scenarios
|
|
pub fn create_hft_parallel_executor() -> Result<ParallelExecutor, MLError> {
|
|
let profile = create_ultra_low_latency_profile();
|
|
ParallelExecutor::new(profile)
|
|
}
|
|
|
|
/// Create latency optimizer with HFT targets
|
|
pub fn create_hft_latency_optimizer() -> LatencyOptimizer {
|
|
LatencyOptimizer::new(50) // 50 microsecond target
|
|
}
|
|
|
|
// ========== CANONICAL TRAINING AND VALIDATION METRICS ==========
|
|
// These are the unified types that all ML modules must use to prevent type conflicts
|
|
|
|
/// Canonical training metrics used throughout ML module
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TrainingMetrics {
|
|
/// Training loss value
|
|
pub loss: f64,
|
|
/// Training accuracy (0.0 to 1.0)
|
|
pub accuracy: f64,
|
|
/// Training precision (0.0 to 1.0)
|
|
pub precision: f64,
|
|
/// Training recall (0.0 to 1.0)
|
|
pub recall: f64,
|
|
/// Training F1 score (0.0 to 1.0)
|
|
pub f1_score: f64,
|
|
/// Total training time in seconds
|
|
pub training_time_seconds: f64,
|
|
/// Number of epochs trained
|
|
pub epochs_trained: u32,
|
|
/// Whether convergence was achieved
|
|
pub convergence_achieved: bool,
|
|
/// Additional model-specific metrics
|
|
pub additional_metrics: HashMap<String, f64>,
|
|
}
|
|
|
|
impl TrainingMetrics {
|
|
/// Create new training metrics
|
|
pub fn new() -> Self {
|
|
Self {
|
|
loss: 0.0,
|
|
accuracy: 0.0,
|
|
precision: 0.0,
|
|
recall: 0.0,
|
|
f1_score: 0.0,
|
|
training_time_seconds: 0.0,
|
|
epochs_trained: 0,
|
|
convergence_achieved: false,
|
|
additional_metrics: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Add an additional metric
|
|
pub fn add_metric(&mut self, name: &str, value: f64) {
|
|
self.additional_metrics.insert(name.to_string(), value);
|
|
}
|
|
|
|
/// Check if training was successful
|
|
pub fn is_successful(&self) -> bool {
|
|
self.convergence_achieved && self.accuracy > 0.5
|
|
}
|
|
}
|
|
|
|
impl Default for TrainingMetrics {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// Canonical validation metrics used throughout ML module
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ValidationMetrics {
|
|
/// Validation loss value
|
|
pub validation_loss: f64,
|
|
/// Validation accuracy (0.0 to 1.0)
|
|
pub validation_accuracy: f64,
|
|
/// Validation precision (0.0 to 1.0)
|
|
pub validation_precision: f64,
|
|
/// Validation recall (0.0 to 1.0)
|
|
pub validation_recall: f64,
|
|
/// Validation F1 score (0.0 to 1.0)
|
|
pub validation_f1_score: f64,
|
|
/// Number of samples validated
|
|
pub samples_validated: usize,
|
|
/// Additional model-specific validation metrics
|
|
pub additional_metrics: HashMap<String, f64>,
|
|
}
|
|
|
|
impl ValidationMetrics {
|
|
/// Create new validation metrics
|
|
pub fn new() -> Self {
|
|
Self {
|
|
validation_loss: 0.0,
|
|
validation_accuracy: 0.0,
|
|
validation_precision: 0.0,
|
|
validation_recall: 0.0,
|
|
validation_f1_score: 0.0,
|
|
samples_validated: 0,
|
|
additional_metrics: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Add an additional validation metric
|
|
pub fn add_metric(&mut self, name: &str, value: f64) {
|
|
self.additional_metrics.insert(name.to_string(), value);
|
|
}
|
|
|
|
/// Check if validation was successful
|
|
pub fn is_successful(&self) -> bool {
|
|
self.validation_accuracy > 0.5 && self.samples_validated > 0
|
|
}
|
|
}
|
|
|
|
impl Default for ValidationMetrics {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
// Public exports moved to end of file after all type definitions
|
|
|
|
// ========== CANONICAL ML TYPES ==========
|
|
// These are the unified types that all ML modules must use to prevent type conflicts
|
|
|
|
/// Canonical inference result used throughout ML module
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct InferenceResult {
|
|
/// Model identifier
|
|
pub model_id: String,
|
|
/// Prediction value (primary prediction)
|
|
pub prediction_value: f64,
|
|
/// Confidence score (0.0 to 1.0)
|
|
pub confidence: f64,
|
|
/// Latency in microseconds
|
|
pub latency_us: u64,
|
|
/// Timestamp in microseconds since UNIX epoch
|
|
pub timestamp: u64,
|
|
/// Model metadata
|
|
pub metadata: ModelMetadata,
|
|
}
|
|
|
|
impl InferenceResult {
|
|
/// Create new inference result
|
|
pub fn new(
|
|
model_id: String,
|
|
prediction_value: f64,
|
|
confidence: f64,
|
|
latency_us: u64,
|
|
timestamp: u64,
|
|
metadata: ModelMetadata,
|
|
) -> Self {
|
|
Self {
|
|
model_id,
|
|
prediction_value,
|
|
confidence,
|
|
latency_us,
|
|
timestamp,
|
|
metadata,
|
|
}
|
|
}
|
|
|
|
/// Extract prediction as float value
|
|
pub fn prediction_as_float(&self) -> f64 {
|
|
self.prediction_value
|
|
}
|
|
|
|
/// Get the model identifier
|
|
pub fn model_id(&self) -> &str {
|
|
&self.model_id
|
|
}
|
|
}
|
|
|
|
/// Canonical model metadata used throughout ML module
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ModelMetadata {
|
|
/// Type of the model
|
|
pub model_type: ModelType,
|
|
/// Model version
|
|
pub version: String,
|
|
/// Number of features used for inference
|
|
pub features_used: usize,
|
|
/// Memory usage in megabytes
|
|
pub memory_usage_mb: f64,
|
|
/// Additional metadata key-value pairs
|
|
pub additional_metadata: HashMap<String, String>,
|
|
}
|
|
|
|
impl ModelMetadata {
|
|
/// Create new model metadata
|
|
pub fn new(
|
|
model_type: ModelType,
|
|
version: String,
|
|
features_used: usize,
|
|
memory_usage_mb: f64,
|
|
) -> Self {
|
|
Self {
|
|
model_type,
|
|
version,
|
|
features_used,
|
|
memory_usage_mb,
|
|
additional_metadata: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Add additional metadata
|
|
pub fn add_metadata(&mut self, key: &str, value: String) {
|
|
self.additional_metadata.insert(key.to_string(), value);
|
|
}
|
|
|
|
/// Mark the model as trained
|
|
pub fn mark_trained(&mut self) {
|
|
self.add_metadata("training_status", "trained".to_owned());
|
|
self.add_metadata(
|
|
"training_timestamp",
|
|
std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_secs()
|
|
.to_string(),
|
|
);
|
|
}
|
|
}
|
|
|
|
// Re-export canonical ModelType from common crate
|
|
pub use ::common::model_types::ModelType;
|
|
|
|
/// Prelude module for convenient imports of commonly used ML types
|
|
///
|
|
/// This module re-exports the most commonly used types and traits from the ML crate
|
|
/// to allow users to import everything they need with a single `use ml::prelude::*;`
|
|
pub mod prelude {
|
|
// Core ML types
|
|
pub use crate::{
|
|
CommonError, CommonTypeError, ErrorCategory, FeatureVector, Features, Feedback,
|
|
HealthStatus, InferenceResult, IntegerTensor, MarketDataSnapshot, MarketRegime,
|
|
ModelMetadata, ModelPrediction, ModelType, Trade, TrainingMetrics, UpdateSummary,
|
|
ValidationMetrics,
|
|
};
|
|
|
|
// Error types
|
|
pub use crate::{MLError, MLResult, UnifiedMLResult};
|
|
|
|
// ML Model trait
|
|
pub use crate::MLModel;
|
|
|
|
// Model registry
|
|
pub use crate::{get_global_registry, ModelRegistry, RegistryStats};
|
|
|
|
// Performance types
|
|
pub use crate::{
|
|
create_hft_latency_optimizer, create_hft_parallel_executor, create_hft_performance_profile,
|
|
create_hft_performance_profile_with_latency, create_ultra_low_latency_profile,
|
|
ExecutorStats, HFTPerformanceProfile, LatencyOptimizer, OptimizationLevel,
|
|
OptimizationRecommendations, ParallelExecutor,
|
|
};
|
|
|
|
// Constants
|
|
pub use crate::{MAX_INFERENCE_LATENCY_US, PRECISION_FACTOR};
|
|
|
|
// GPU device management
|
|
pub use crate::gpu::{capabilities::GpuCapabilities, DeviceConfig};
|
|
|
|
// Tensor types from candle
|
|
pub use candle_core::{Device, Tensor};
|
|
pub use candle_nn::{Module, VarBuilder, VarMap};
|
|
|
|
// Common external types
|
|
pub use rust_decimal::Decimal;
|
|
pub use serde::{Deserialize, Serialize};
|
|
|
|
// Data pipeline
|
|
pub use crate::data_pipeline::{DatasetManager, DatasetMode, DatasetSpec, PreparedDataset};
|
|
|
|
// Asset selection
|
|
pub use crate::asset_selection::{ActiveSetSelector, AssetUniverse, PredictabilityScorer};
|
|
}
|