//! # Feature Engineering for Financial ML Models //! //! Comprehensive feature engineering pipeline for HFT trading systems that transforms //! raw market data into meaningful features for machine learning models. This module //! provides a complete toolkit for quantitative finance feature extraction. //! //! ## Core Components //! //! ### Technical Indicators //! - **Moving Averages**: Simple (SMA) and Exponential (EMA) moving averages //! - **Momentum**: RSI, MACD, momentum oscillators //! - **Volatility**: Bollinger Bands, Average True Range //! - **Volume**: Volume-weighted indicators and flow analysis //! //! ### Market Microstructure Features //! - **Spreads**: Bid-ask spread analysis and Roll spread estimation //! - **Imbalances**: Volume and order book imbalances //! - **Price Impact**: Kyle's lambda, Amihud illiquidity ratio //! - **Liquidity**: Market depth and liquidity scoring //! //! ### TLOB (Time-Limited Order Book) Features //! - **Order Flow**: Order flow imbalance and directional analysis //! - **Book Dynamics**: Order book shape and dynamics //! - **Execution Quality**: Slippage and execution cost analysis //! //! ### Temporal Features //! - **Cyclical**: Hour of day, day of week patterns //! - **Market Sessions**: Pre-market, regular hours, after-market //! - **Calendar Effects**: Month-end, quarter-end, holiday effects //! //! ### Portfolio & Risk Features //! - **Performance**: P&L tracking, Sharpe ratio, Sortino ratio //! - **Risk Metrics**: VaR, Expected Shortfall, Maximum Drawdown //! - **Exposure**: Beta, correlation analysis //! //! ## Architecture //! //! ```text //! ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ //! │ Raw Market │────│ Feature │────│ ML Model │ //! │ Data │ │ Engineering │ │ Input │ //! └─────────────────┘ └─────────────────┘ └─────────────────┘ //! │ │ │ //! ▼ ▼ ▼ //! ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ //! │ Tick Data │ │ Technical │ │ Feature │ //! │ Order Books │────│ Indicators │────│ Vectors │ //! │ Trade Flow │ │ Microstructure│ │ (Normalized) │ //! └─────────────────┘ └─────────────────┘ └─────────────────┘ //! ``` //! //! ## Performance Considerations //! //! - **Streaming Updates**: Incremental calculations for real-time processing //! - **Memory Efficiency**: Rolling windows with automatic cleanup //! - **SIMD Optimization**: Vectorized computations where possible //! - **Caching**: Intelligent caching of intermediate calculations //! //! ## Usage Examples //! //! ```rust //! use data::features::{ //! TechnicalIndicators, MicrostructureAnalyzer, TemporalFeatures, //! FeatureVector, PricePoint //! }; //! use config::data_config::TechnicalIndicatorsConfig; //! use chrono::Utc; //! //! // Initialize technical indicators //! let config = TechnicalIndicatorsConfig { //! ma_periods: vec![10, 20, 50], //! rsi_periods: vec![14], //! bollinger_periods: vec![20], //! // ... other config //! }; //! //! let mut indicators = TechnicalIndicators::new(config); //! //! // Update with new price data //! let price_point = PricePoint { //! timestamp: Utc::now(), //! open: 100.0, //! high: 101.5, //! low: 99.5, //! close: 101.0, //! }; //! //! indicators.update_price("AAPL", price_point); //! //! // Extract features //! let tech_features = indicators.calculate_features("AAPL"); //! let temporal_features = TemporalFeatures::extract_features(Utc::now()); //! //! // Combine into feature vector //! let mut all_features = tech_features; //! all_features.extend(temporal_features); //! //! let feature_vector = FeatureVector { //! timestamp: Utc::now(), //! symbol: "AAPL".to_string(), //! features: all_features, //! metadata: FeatureMetadata::default(), //! }; //! ``` //! //! ## Feature Categories //! //! Features are organized into categories for better model interpretation: //! //! - **Price**: OHLC-based features and price transformations //! - **Volume**: Volume-based indicators and flow analysis //! - **TechnicalIndicator**: Traditional TA indicators (RSI, MACD, etc.) //! - **Microstructure**: Market microstructure and liquidity features //! - **Temporal**: Time-based features and calendar effects //! - **Regime**: Market regime and volatility state features //! - **TLOB**: Time-Limited Order Book specific features //! - **Portfolio**: Portfolio-level performance and risk metrics //! - **Risk**: Risk management and exposure metrics use chrono::{DateTime, Datelike, Timelike, Utc}; use config::data_config::{ DataMicrostructureConfig as MicrostructureConfig, DataTLOBConfig as TLOBConfig, DataTechnicalIndicatorsConfig as TechnicalIndicatorsConfig, }; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap, VecDeque}; /// Feature vector for ML model training and inference. /// /// Represents a complete set of features extracted from market data at a specific /// point in time. This is the primary data structure used to feed machine learning /// models in the trading system. /// /// # Structure /// /// - **Timestamp**: When these features were calculated /// - **Symbol**: The financial instrument these features apply to /// - **Features**: Key-value map of feature names to numerical values /// - **Metadata**: Additional information about feature quality and categories /// /// # Feature Organization /// /// Features are stored as a flat HashMap for efficient access, but can be /// categorized using the metadata for model interpretation and debugging. /// /// # Normalization /// /// Features should be normalized before training ML models. The feature /// engineering pipeline can apply various normalization techniques: /// - Z-score normalization (mean=0, std=1) /// - Min-max scaling (0-1 range) /// - Robust scaling (using percentiles) /// /// # Examples /// /// ```rust /// use data::features::{FeatureVector, FeatureMetadata, FeatureCategory}; /// use std::collections::HashMap; /// use chrono::Utc; /// /// let mut features = HashMap::new(); /// features.insert("sma_20".to_string(), 150.25); /// features.insert("rsi_14".to_string(), 65.8); /// features.insert("volume_ratio".to_string(), 1.2); /// /// let feature_vector = FeatureVector { /// timestamp: Utc::now(), /// symbol: "AAPL".to_string(), /// features, /// metadata: FeatureMetadata::default(), /// }; /// /// // Access specific features /// let sma_value = feature_vector.features.get("sma_20").unwrap(); /// assert_eq!(*sma_value, 150.25); /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FeatureVector { /// Timestamp when these features were calculated /// /// UTC timestamp indicating the exact time these features represent. /// Critical for time-series analysis and ensuring proper temporal ordering. pub timestamp: DateTime, /// Financial instrument symbol (ticker) /// /// The security identifier (e.g., "AAPL", "SPY", "EURUSD") that these /// features were calculated for. Used for symbol-specific model training. pub symbol: String, /// Feature name-value pairs /// /// Map of feature names to their calculated numerical values. /// Feature names should be descriptive and consistent across time /// (e.g., "sma_20", "rsi_14", "bid_ask_spread_bps"). pub features: HashMap, /// Feature metadata and quality information /// /// Additional information about the features including descriptions, /// categories, and data quality indicators. pub metadata: FeatureMetadata, } /// Metadata and quality information for feature vectors. /// /// Provides additional context about features including descriptions, /// categorization, and data quality metrics. Essential for feature /// interpretation, model debugging, and data quality monitoring. /// /// # Quality Indicators /// /// Quality indicators help identify potential data issues: /// - **Completeness**: Percentage of non-null values (0.0-1.0) /// - **Freshness**: How recent the underlying data is (0.0-1.0) /// - **Reliability**: Confidence in data accuracy (0.0-1.0) /// - **Stability**: Variance stability over time (0.0-1.0) /// /// # Feature Categories /// /// Categorization helps with: /// - Feature selection and importance analysis /// - Model interpretation and explainability /// - Feature engineering pipeline organization /// - Regulatory compliance and audit trails /// /// # Examples /// /// ```rust /// use data::features::{FeatureMetadata, FeatureCategory}; /// use std::collections::HashMap; /// /// let mut descriptions = HashMap::new(); /// descriptions.insert("sma_20".to_string(), "20-period Simple Moving Average".to_string()); /// descriptions.insert("rsi_14".to_string(), "14-period Relative Strength Index".to_string()); /// /// let mut categories = HashMap::new(); /// categories.insert("sma_20".to_string(), FeatureCategory::TechnicalIndicator); /// categories.insert("rsi_14".to_string(), FeatureCategory::TechnicalIndicator); /// /// let mut quality = HashMap::new(); /// quality.insert("sma_20".to_string(), 0.98); // 98% data completeness /// quality.insert("rsi_14".to_string(), 0.95); // 95% data completeness /// /// let metadata = FeatureMetadata { /// feature_descriptions: descriptions, /// feature_categories: categories, /// quality_indicators: quality, /// }; /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FeatureMetadata { /// Human-readable descriptions of each feature /// /// Maps feature names to their detailed descriptions explaining /// what the feature represents and how it's calculated. /// Essential for model documentation and interpretation. pub feature_descriptions: HashMap, /// Categorical classification of features /// /// Maps feature names to their category types for organization /// and analysis. Helps with feature selection and model interpretation. pub feature_categories: HashMap, /// Data quality metrics for each feature (0.0-1.0) /// /// Maps feature names to quality scores indicating reliability, /// completeness, and freshness of the underlying data. /// Used for automated quality monitoring and alerts. pub quality_indicators: HashMap, /// Symbol these features belong to (for tests) pub symbol: String, /// Timestamp when metadata was created (for tests) pub timestamp: DateTime, /// Total count of features (for tests) pub feature_count: usize, /// List of categories present (for tests) pub categories: Vec, } /// Categorical classification system for organizing features. /// /// Provides a hierarchical way to organize features based on their /// data source, calculation method, and business purpose. This /// categorization is essential for: /// /// - **Feature Selection**: Group-based importance analysis /// - **Model Interpretation**: Understanding feature contributions by category /// - **Data Lineage**: Tracking feature dependencies and data sources /// - **Regulatory Compliance**: Documenting model inputs by data type /// - **Performance Monitoring**: Category-specific quality metrics /// /// # Category Descriptions /// /// - **Price**: Features derived from OHLC price data /// - **Volume**: Features based on trading volume and turnover /// - **TechnicalIndicator**: Traditional technical analysis indicators /// - **Microstructure**: Market microstructure and liquidity metrics /// - **Temporal**: Time-based features and calendar effects /// - **Regime**: Market regime and volatility state indicators /// - **TLOB**: Time-Limited Order Book specific features /// - **Portfolio**: Portfolio-level performance and allocation metrics /// - **Risk**: Risk management and exposure indicators /// /// # Usage in Feature Selection /// /// ```rust /// use data::features::{FeatureCategory, FeatureMetadata}; /// use std::collections::HashMap; /// /// fn filter_technical_indicators(metadata: &FeatureMetadata) -> Vec { /// metadata.feature_categories /// .iter() /// .filter(|(_, category)| matches!(category, FeatureCategory::TechnicalIndicator)) /// .map(|(name, _)| name.clone()) /// .collect() /// } /// ``` #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub enum FeatureCategory { /// Price-based features (OHLC, returns, price ratios) /// /// Features derived directly from price data including: /// - Open, High, Low, Close prices and their transformations /// - Price returns and log returns /// - Price ratios and relative price movements /// - Gap analysis and price range metrics Price, /// Volume-based features (volume, turnover, VWAP) /// /// Features calculated from trading volume data including: /// - Raw volume and volume ratios /// - Volume-weighted average price (VWAP) /// - Volume rate of change /// - Dollar volume and turnover metrics Volume, /// Traditional technical analysis indicators /// /// Classic technical indicators including: /// - Moving averages (SMA, EMA, WMA) /// - Momentum indicators (RSI, MACD, Stochastic) /// - Volatility indicators (Bollinger Bands, ATR) /// - Trend indicators (ADX, Parabolic SAR) TechnicalIndicator, /// Market microstructure and liquidity features /// /// Features related to market structure and liquidity including: /// - Bid-ask spreads and spread components /// - Order book imbalances and depth /// - Price impact measures (Kyle's lambda, Amihud ratio) /// - Trade classification and flow analysis Microstructure, /// Time-based and calendar features /// /// Features derived from timestamps and calendar patterns: /// - Hour of day, day of week effects /// - Market session indicators (pre-market, regular hours) /// - Calendar effects (month-end, quarter-end, holidays) /// - Seasonal and cyclical patterns Temporal, /// Market regime and volatility state features /// /// Features that capture market regime changes: /// - Volatility regime indicators /// - Trend vs. mean-reverting states /// - Correlation regime changes /// - Market stress indicators Regime, /// Time-Limited Order Book (TLOB) specific features /// /// Features derived from order book dynamics: /// - Order flow imbalances /// - Book shape and slope analysis /// - Order arrival and cancellation patterns /// - Liquidity provision patterns TLOB, /// Portfolio-level performance and allocation features /// /// Features calculated at the portfolio level: /// - Portfolio returns and risk metrics /// - Sector and style allocations /// - Concentration and diversification measures /// - Performance attribution factors Portfolio, /// Risk management and exposure features /// /// Features related to risk measurement and control: /// - Value at Risk (VaR) estimates /// - Maximum drawdown metrics /// - Exposure concentrations /// - Correlation and beta measurements Risk, } /// Technical indicators calculator for financial time series analysis. /// /// Provides a comprehensive suite of technical analysis indicators commonly used /// in quantitative trading and machine learning models. Supports streaming /// calculations with automatic data management and efficient memory usage. /// /// # Supported Indicators /// /// - **Moving Averages**: Simple (SMA) and Exponential (EMA) moving averages /// - **Momentum**: Relative Strength Index (RSI) with configurable periods /// - **MACD**: Moving Average Convergence Divergence with signal line /// - **Bollinger Bands**: Price bands with standard deviation channels /// - **Volume Indicators**: Volume-based technical indicators /// /// # Performance Features /// /// - **Streaming Updates**: Incremental calculations for real-time processing /// - **Memory Management**: Automatic cleanup of old data based on largest period /// - **Multiple Timeframes**: Support for different periods simultaneously /// - **State Persistence**: Maintains indicator state for continuous calculations /// /// # Configuration /// /// The calculator is configured via `TechnicalIndicatorsConfig` which specifies: /// - Moving average periods (e.g., 10, 20, 50, 200) /// - RSI calculation periods (e.g., 14, 21) /// - Bollinger Bands periods and standard deviations /// - MACD parameters (fast, slow, signal periods) /// /// # Examples /// /// ```rust /// use data::features::{TechnicalIndicators, PricePoint}; /// use config::data_config::TechnicalIndicatorsConfig; /// use chrono::Utc; /// /// let config = TechnicalIndicatorsConfig { /// ma_periods: vec![10, 20, 50], /// rsi_periods: vec![14], /// bollinger_periods: vec![20], /// // ... other configuration /// }; /// /// let mut indicators = TechnicalIndicators::new(config); /// /// // Add price data /// let price_point = PricePoint { /// timestamp: Utc::now(), /// open: 100.0, /// high: 102.0, /// low: 99.0, /// close: 101.5, /// }; /// /// indicators.update_price("AAPL", price_point); /// /// // Calculate all features /// let features = indicators.calculate_features("AAPL"); /// /// // Access specific indicators /// if let Some(sma_20) = features.get("sma_20") { /// println!("20-period SMA: {}", sma_20); /// } /// ``` /// /// # Data Requirements /// /// Different indicators have different minimum data requirements: /// - **SMA/EMA**: Requires at least 'period' data points /// - **RSI**: Requires at least 'period + 1' data points /// - **MACD**: Requires sufficient data for slow EMA calculation /// - **Bollinger Bands**: Requires at least 'period' data points /// /// Features will be omitted from output until sufficient data is available. pub struct TechnicalIndicators { pub config: TechnicalIndicatorsConfig, pub price_data: BTreeMap>, pub volume_data: BTreeMap>, pub indicators: BTreeMap, } /// OHLC price data point for technical indicator calculations. /// /// Represents a single price observation with open, high, low, and close prices /// along with a timestamp. This is the fundamental data unit for all technical /// indicator calculations in the system. /// /// # Data Integrity /// /// Price points should satisfy basic integrity constraints: /// - High >= max(Open, Close) /// - Low <= min(Open, Close) /// - All prices should be positive /// - Timestamps should be in chronological order /// /// # Usage in Indicators /// /// Different indicators use different price components: /// - **Moving Averages**: Typically use Close price /// - **Bollinger Bands**: Use Close price for center line /// - **ATR**: Uses High, Low, and previous Close /// - **Price Channels**: Use High and Low prices /// /// # Examples /// /// ```rust /// use data::features::PricePoint; /// use chrono::Utc; /// /// let price_point = PricePoint { /// timestamp: Utc::now(), /// open: 100.0, /// high: 102.5, // Must be >= max(open, close) /// low: 99.0, // Must be <= min(open, close) /// close: 101.5, /// }; /// /// // Validate price point integrity /// assert!(price_point.high >= price_point.open.max(price_point.close)); /// assert!(price_point.low <= price_point.open.min(price_point.close)); /// ``` /// /// # Time Series Usage /// /// When building time series for indicators: /// - Maintain chronological order by timestamp /// - Handle gaps in data appropriately /// - Consider timezone consistency (use UTC) /// - Validate price continuity and outliers #[derive(Debug, Clone)] pub struct PricePoint { /// Timestamp of this price observation /// /// UTC timestamp indicating when this price data represents. /// Should be consistent with the timeframe being analyzed. pub timestamp: DateTime, /// Opening price for the period /// /// The first traded price during the time period. /// For continuous markets, this is the first price after the previous close. pub open: f64, /// Highest price during the period /// /// Must be greater than or equal to both open and close prices. /// Used in volatility and range-based indicators. pub high: f64, /// Lowest price during the period /// /// Must be less than or equal to both open and close prices. /// Used in volatility and support/resistance analysis. pub low: f64, /// Closing price for the period /// /// The last traded price during the time period. /// Most commonly used price in technical indicators. pub close: f64, } /// Volume data point for volume-based indicator calculations. /// /// Represents trading volume information for a specific time period, /// including both raw volume and volume-weighted average price (VWAP). /// Essential for volume-based technical indicators and market analysis. /// /// # Volume Metrics /// /// - **Volume**: Total number of shares/contracts traded /// - **VWAP**: Volume-weighted average price for the period /// - **Dollar Volume**: Volume * Average Price (derived) /// /// # Applications /// /// Volume data is used in: /// - Volume moving averages and oscillators /// - On-Balance Volume (OBV) calculations /// - Volume Rate of Change (VROC) /// - Accumulation/Distribution indicators /// - Money Flow Index (MFI) /// /// # Data Quality /// /// Volume data should be validated for: /// - Non-negative volume values /// - Reasonable VWAP relative to price range /// - Consistency with price data timestamps /// - Outlier detection for unusual volume spikes /// /// # Examples /// /// ```rust /// use data::features::VolumePoint; /// use chrono::Utc; /// /// let volume_point = VolumePoint { /// timestamp: Utc::now(), /// volume: 1_000_000.0, // 1M shares traded /// volume_weighted_price: 150.25, // VWAP for the period /// }; /// /// // Calculate dollar volume /// let dollar_volume = volume_point.volume * volume_point.volume_weighted_price; /// assert_eq!(dollar_volume, 150_250_000.0); // $150.25M traded /// ``` #[derive(Debug, Clone)] pub struct VolumePoint { /// Timestamp of this volume observation /// /// UTC timestamp corresponding to the same period as the associated price data. pub timestamp: DateTime, /// Total volume traded during the period /// /// Number of shares, contracts, or units traded. Should be non-negative /// and represent the total trading activity for the time period. pub volume: f64, /// Volume-weighted average price (VWAP) for the period /// /// The average price weighted by trading volume, calculated as: /// VWAP = Σ(Price × Volume) / Σ(Volume) /// Provides a more representative average price than simple arithmetic mean. pub volume_weighted_price: f64, /// Buy-side volume for the period (for tests) pub buy_volume: f64, /// Sell-side volume for the period (for tests) pub sell_volume: f64, } /// Internal state for maintaining technical indicator calculations. /// /// Stores the current state of all technical indicators for a specific symbol, /// enabling efficient incremental updates without recalculating from scratch. /// This state persistence is crucial for real-time trading systems where /// performance is critical. /// /// # State Components /// /// - **SMA**: Simple moving average values for different periods /// - **EMA**: Exponential moving average values and their smoothing states /// - **RSI**: Relative Strength Index values with gain/loss tracking /// - **MACD**: Complete MACD state including EMA components /// - **Bollinger**: Bollinger Bands state with statistics /// /// # Memory Management /// /// The state automatically manages memory by: /// - Storing only current indicator values, not full history /// - Using efficient data structures for O(1) updates /// - Automatically cleaning up unused indicators /// /// # Thread Safety /// /// This structure is designed for single-threaded use per symbol. /// For multi-threaded access, wrap in appropriate synchronization primitives. /// /// # Examples /// /// ```rust /// use data::features::IndicatorState; /// use std::collections::HashMap; /// /// // State is typically managed internally by TechnicalIndicators /// let mut state = IndicatorState { /// sma: HashMap::new(), /// ema: HashMap::new(), /// rsi: HashMap::new(), /// macd: Default::default(), /// bollinger: HashMap::new(), /// }; /// /// // Access specific indicator values /// if let Some(sma_20) = state.sma.get(&20) { /// println!("20-period SMA: {}", sma_20); /// } /// ``` #[derive(Debug, Clone)] pub struct IndicatorState { /// Simple Moving Average values by period /// /// Maps period lengths to their current SMA values. /// Updated with each new price point using rolling window calculation. pub sma: HashMap, /// Exponential Moving Average values by period /// /// Maps period lengths to their current EMA values. /// Maintains smoothing state for efficient incremental updates. pub ema: HashMap, /// Relative Strength Index values by period /// /// Maps RSI periods to their current RSI values (0-100 scale). /// Internally tracks average gains and losses for calculation. pub rsi: HashMap, /// MACD (Moving Average Convergence Divergence) state /// /// Complete MACD calculation state including MACD line, signal line, /// histogram, and underlying EMA components. pub macd: MACDState, /// Bollinger Bands state by period /// /// Maps periods to complete Bollinger Bands state including /// upper/lower bands, bandwidth, and %B calculations. pub bollinger: HashMap, } /// MACD (Moving Average Convergence Divergence) indicator state. /// /// Maintains the complete state for MACD calculation including the main MACD line, /// signal line, histogram, and underlying exponential moving averages. MACD is /// a trend-following momentum indicator that shows relationships between two /// moving averages of prices. /// /// # MACD Components /// /// - **MACD Line**: Fast EMA - Slow EMA (typically 12-day - 26-day) /// - **Signal Line**: EMA of MACD line (typically 9-day) /// - **Histogram**: MACD Line - Signal Line /// /// # Signal Interpretation /// /// - **Bullish Signal**: MACD line crosses above signal line /// - **Bearish Signal**: MACD line crosses below signal line /// - **Momentum**: Histogram increasing = strengthening trend /// - **Divergence**: MACD vs price divergence = potential reversal /// /// # State Persistence /// /// The state maintains the underlying EMA calculations to enable /// efficient incremental updates without recalculating the entire history. /// /// # Examples /// /// ```rust /// use data::features::MACDState; /// /// let macd_state = MACDState { /// macd_line: 1.25, // Fast EMA - Slow EMA /// signal_line: 0.95, // EMA of MACD line /// histogram: 0.30, // MACD line - Signal line /// fast_ema: 150.25, // Current fast EMA value /// slow_ema: 149.00, // Current slow EMA value /// signal_ema: 0.95, // Signal line EMA value /// }; /// /// // Check for bullish crossover /// let is_bullish = macd_state.macd_line > macd_state.signal_line && /// macd_state.histogram > 0.0; /// ``` #[derive(Debug, Clone)] pub struct MACDState { /// MACD line value (Fast EMA - Slow EMA) /// /// The main MACD indicator calculated as the difference between /// fast and slow exponential moving averages. Positive values /// indicate upward momentum, negative values indicate downward momentum. pub macd_line: f64, /// Signal line value (EMA of MACD line) /// /// The signal line is an exponential moving average of the MACD line, /// used to generate buy/sell signals through crossovers with the MACD line. pub signal_line: f64, /// MACD histogram (MACD line - Signal line) /// /// The difference between MACD line and signal line, displayed as /// a histogram. Shows the convergence and divergence of the two lines. pub histogram: f64, /// Last update timestamp (for tests) pub last_update: DateTime, /// Current fast EMA value /// /// The faster exponential moving average component (typically 12-period). /// Maintained for efficient incremental calculation updates. pub fast_ema: f64, /// Current slow EMA value /// /// The slower exponential moving average component (typically 26-period). /// Maintained for efficient incremental calculation updates. pub slow_ema: f64, /// Current signal line EMA value /// /// The EMA used for the signal line calculation (typically 9-period). /// Maintained for efficient incremental calculation updates. pub signal_ema: f64, } /// Bollinger Bands indicator state and calculations. /// /// Bollinger Bands consist of a middle line (moving average) and two bands /// (upper and lower) that are standard deviations away from the middle line. /// They are used to identify overbought/oversold conditions and volatility. /// /// # Band Components /// /// - **Upper Band**: Middle line + (2 × Standard Deviation) /// - **Middle Band**: Simple Moving Average (typically 20-period) /// - **Lower Band**: Middle line - (2 × Standard Deviation) /// /// # Derived Metrics /// /// - **Bandwidth**: (Upper Band - Lower Band) / Middle Band /// - **%B**: (Price - Lower Band) / (Upper Band - Lower Band) /// /// # Trading Signals /// /// - **Bollinger Squeeze**: Low bandwidth indicates low volatility /// - **Band Walking**: Price staying near upper/lower band indicates strong trend /// - **Mean Reversion**: Price touching bands often reverses toward middle /// - **Breakouts**: Price breaking outside bands can signal trend continuation /// /// # Examples /// /// ```rust /// use data::features::BollingerBandsState; /// /// let bb_state = BollingerBandsState { /// upper_band: 152.50, /// middle_band: 150.00, // 20-period SMA /// lower_band: 147.50, /// bandwidth: 0.033, // 3.3% bandwidth /// percent_b: 0.75, // Price at 75% of band range /// }; /// /// // Check for squeeze condition (low volatility) /// let is_squeeze = bb_state.bandwidth < 0.05; // Less than 5% /// /// // Check overbought/oversold conditions /// let is_overbought = bb_state.percent_b > 0.8; /// let is_oversold = bb_state.percent_b < 0.2; /// ``` #[derive(Debug, Clone)] pub struct BollingerBandsState { /// Upper Bollinger Band (Middle + 2 × Std Dev) /// /// The upper boundary of the Bollinger Bands, typically set at /// 2 standard deviations above the middle line. Prices touching /// or exceeding this level may indicate overbought conditions. pub upper_band: f64, /// Middle Bollinger Band (Simple Moving Average) /// /// The center line of Bollinger Bands, typically a 20-period /// simple moving average. Acts as dynamic support/resistance. pub middle_band: f64, /// Lower Bollinger Band (Middle - 2 × Std Dev) /// /// The lower boundary of the Bollinger Bands, typically set at /// 2 standard deviations below the middle line. Prices touching /// or falling below this level may indicate oversold conditions. pub lower_band: f64, /// Bandwidth ratio ((Upper - Lower) / Middle) /// /// Measures the width of the bands relative to the middle band. /// Low bandwidth indicates low volatility ("squeeze"), /// high bandwidth indicates high volatility. pub bandwidth: f64, /// %B indicator ((Price - Lower) / (Upper - Lower)) /// /// Shows where the price is relative to the bands: /// - 1.0 = At upper band /// - 0.5 = At middle band /// - 0.0 = At lower band /// Values outside 0-1 indicate price outside the bands. pub percent_b: f64, /// Last update timestamp (for tests) pub last_update: DateTime, } /// Market microstructure analyzer for liquidity and trading cost analysis. /// /// Analyzes the detailed structure of financial markets including bid-ask spreads, /// order book dynamics, price impact, and liquidity measures. Essential for /// high-frequency trading strategies and execution cost analysis. /// /// # Microstructure Features /// /// - **Spreads**: Bid-ask spread, effective spread, Roll spread /// - **Imbalances**: Volume imbalance, order book imbalance /// - **Price Impact**: Kyle's lambda, Amihud illiquidity ratio /// - **Liquidity**: Market depth, liquidity scores /// - **Trade Classification**: Buy/sell classification, trade direction /// /// # Data Sources /// /// The analyzer processes multiple data streams: /// - **Order Books**: Level 1 and Level 2 market data /// - **Trades**: Time and sales data with trade classification /// - **Quotes**: Bid/ask quotes with sizes /// /// # Applications /// /// - **Execution Analysis**: Measuring transaction costs and market impact /// - **Market Making**: Optimal spread setting and inventory management /// - **Regime Detection**: Identifying changes in market liquidity conditions /// - **Risk Management**: Liquidity risk assessment and monitoring /// /// # Performance Considerations /// /// - **Real-time Processing**: Designed for sub-millisecond latency /// - **Memory Efficiency**: Rolling windows for historical calculations /// - **Configurable Features**: Enable only needed calculations for performance /// /// # Examples /// /// ```rust /// use data::features::{MicrostructureAnalyzer, QuoteData, TradeData}; /// use config::data_config::MicrostructureConfig; /// use chrono::Utc; /// /// let config = MicrostructureConfig { /// bid_ask_spread: true, /// volume_imbalance: true, /// price_impact: true, /// kyle_lambda: false, // Computationally expensive /// amihud_ratio: true, /// // ... other settings /// }; /// /// let mut analyzer = MicrostructureAnalyzer::new(config); /// /// // Update with market data /// let quote = QuoteData { /// timestamp: Utc::now(), /// bid: 149.95, /// ask: 150.05, /// bid_size: 1000.0, /// ask_size: 800.0, /// }; /// analyzer.update_quote("AAPL", quote); /// /// // Calculate microstructure features /// let features = analyzer.calculate_features("AAPL"); /// /// if let Some(spread_bps) = features.get("bid_ask_spread_bps") { /// println!("Bid-ask spread: {} bps", spread_bps); /// } /// ``` pub struct MicrostructureAnalyzer { pub config: MicrostructureConfig, pub order_books: HashMap, pub trade_data: BTreeMap>, pub quote_data: BTreeMap>, } /// Order book state snapshot for microstructure analysis. /// /// Represents a point-in-time view of the order book including bids, asks, /// and derived metrics like spread and imbalance. Used for calculating /// market microstructure features and liquidity analysis. /// /// # Order Book Metrics /// /// - **Mid Price**: (Best Bid + Best Ask) / 2 /// - **Spread**: Best Ask - Best Bid (absolute) /// - **Imbalance**: (Bid Size - Ask Size) / (Bid Size + Ask Size) /// - **Depth**: Total volume available at best levels /// /// # Data Quality /// /// Order book states should be validated for: /// - Best bid < Best ask (no crossed market) /// - Positive sizes for all price levels /// - Proper price level ordering (ascending asks, descending bids) /// - Timestamp consistency with market data /// /// # Applications /// /// - **Spread Analysis**: Transaction cost estimation /// - **Liquidity Assessment**: Available depth and market impact /// - **Imbalance Signals**: Short-term price direction prediction /// - **Market Making**: Optimal quote placement strategies /// /// # Examples /// /// ```rust /// use data::features::OrderBookState; /// use common::PriceLevel; /// use chrono::Utc; /// /// let order_book = OrderBookState { /// timestamp: Utc::now(), /// bids: vec![ /// PriceLevel { price: 149.95.into(), size: 1000.into() }, /// PriceLevel { price: 149.90.into(), size: 1500.into() }, /// ], /// asks: vec![ /// PriceLevel { price: 150.05.into(), size: 800.into() }, /// PriceLevel { price: 150.10.into(), size: 1200.into() }, /// ], /// mid_price: 150.00, /// spread: 0.10, /// imbalance: 0.111, // (1000-800)/(1000+800) /// depth: 1800.0, // Total at best levels /// }; /// ``` #[derive(Debug, Clone)] pub struct OrderBookState { pub timestamp: DateTime, pub best_bid: f64, pub best_ask: f64, pub bid_size: f64, pub ask_size: f64, pub spread: f64, pub mid_price: f64, } // PriceLevel moved to canonical source in common::types // Note: Original used f64 types - code may need conversion to Decimal /// Individual trade data for microstructure analysis. /// /// Represents a single trade execution with price, size, direction, and timing /// information. Used for calculating trade-based microstructure features like /// price impact, trade classification, and flow analysis. /// /// # Trade Classification /// /// Trade direction is classified using various methods: /// - **Tick Rule**: Compare trade price to previous trade price /// - **Quote Rule**: Compare trade price to midpoint of bid-ask /// - **LR Algorithm**: Lee-Ready algorithm combining tick and quote rules /// - **Bulk Volume Classification**: Statistical methods for block trades /// /// # Applications /// /// - **Order Flow Analysis**: Measuring buy vs sell pressure /// - **Price Impact**: Analyzing effect of trades on subsequent prices /// - **Trade Size Analysis**: Volume distribution and block trade detection /// - **Execution Quality**: Measuring implementation shortfall and slippage /// /// # Data Quality /// /// Trade data should be validated for: /// - Positive trade sizes /// - Reasonable prices within market ranges /// - Proper timestamp ordering /// - Trade direction consistency with price movements /// /// # Examples /// /// ```rust /// use data::features::{TradeData, TradeDirection}; /// use chrono::Utc; /// /// let trade = TradeData { /// timestamp: Utc::now(), /// price: 150.05, /// size: 1000.0, /// direction: TradeDirection::Buy, // Classified as buyer-initiated /// }; /// /// // Calculate trade value /// let trade_value = trade.price * trade.size; // $150,050 /// /// // Check for block trade /// let is_block_trade = trade.size > 10_000.0; /// ``` #[derive(Debug, Clone)] pub struct TradeData { pub timestamp: DateTime, pub price: f64, pub size: f64, pub direction: TradeDirection, pub conditions: Vec, } /// Quote data (bid/ask prices and sizes) for microstructure analysis. /// /// Represents the best bid and offer (BBO) at a specific point in time, /// including both prices and sizes. Essential for calculating spreads, /// imbalances, and other microstructure features. /// /// # Quote Components /// /// - **Bid**: Highest price buyers are willing to pay /// - **Ask**: Lowest price sellers are willing to accept /// - **Bid Size**: Quantity available at the bid price /// - **Ask Size**: Quantity available at the ask price /// /// # Derived Metrics /// /// From quote data, we can calculate: /// - **Spread**: Ask - Bid /// - **Mid Price**: (Ask + Bid) / 2 /// - **Imbalance**: (Bid Size - Ask Size) / (Bid Size + Ask Size) /// - **Depth**: Bid Size + Ask Size /// /// # Data Integrity /// /// Quote data should satisfy: /// - Ask price >= Bid price (no crossed market in normal conditions) /// - Positive sizes for both bid and ask /// - Reasonable spread relative to price level /// - Timestamps in chronological order /// /// # Examples /// /// ```rust /// use data::features::QuoteData; /// use chrono::Utc; /// /// let quote = QuoteData { /// timestamp: Utc::now(), /// bid: 149.95, /// ask: 150.05, /// bid_size: 1000.0, /// ask_size: 800.0, /// }; /// /// // Calculate derived metrics /// let spread = quote.ask - quote.bid; // 0.10 /// let mid_price = (quote.ask + quote.bid) / 2.0; // 150.00 /// let imbalance = (quote.bid_size - quote.ask_size) / // 0.111 /// (quote.bid_size + quote.ask_size); /// ``` #[derive(Debug, Clone)] pub struct QuoteData { pub timestamp: DateTime, pub bid_price: f64, pub ask_price: f64, pub bid_size: f64, pub ask_size: f64, pub exchange: String, } /// Classification of trade direction for order flow analysis. /// /// Determines whether a trade was initiated by a buyer (market buy order) /// or seller (market sell order), which is crucial for understanding /// order flow and price pressure in the market. /// /// # Classification Methods /// /// - **Tick Rule**: Compare to previous trade price /// - Uptick (higher price) = Buy /// - Downtick (lower price) = Sell /// - Zero tick = Use previous classification /// /// - **Quote Rule**: Compare to bid-ask midpoint /// - Above midpoint = Buy /// - Below midpoint = Sell /// - At midpoint = Unknown /// /// - **Lee-Ready Algorithm**: Combination of tick and quote rules /// - Use quote rule first /// - If at midpoint, use tick rule /// /// # Applications /// /// - **Order Flow Imbalance**: Net buying vs selling pressure /// - **Price Impact**: Effect of buyer vs seller initiated trades /// - **Market Microstructure**: Understanding trade dynamics /// - **Execution Analysis**: Assessing market impact of strategies /// /// # Examples /// /// ```rust /// use data::features::TradeDirection; /// /// // Classify trades based on price movement /// fn classify_by_tick_rule(current_price: f64, previous_price: f64) -> TradeDirection { /// if current_price > previous_price { /// TradeDirection::Buy /// } else if current_price < previous_price { /// TradeDirection::Sell /// } else { /// TradeDirection::Unknown /// } /// } /// ``` #[derive(Debug, Clone)] pub enum TradeDirection { /// Buyer-initiated trade (market buy order) /// /// Trade was initiated by an aggressive buyer using a market order /// to purchase at the best available ask price. Indicates positive /// price pressure and buying interest. Buy, /// Seller-initiated trade (market sell order) /// /// Trade was initiated by an aggressive seller using a market order /// to sell at the best available bid price. Indicates negative /// price pressure and selling interest. Sell, /// Unknown or indeterminate trade direction /// /// Trade direction could not be determined reliably, either due to: /// - Trade at exact midpoint with no prior price reference /// - Insufficient data for classification algorithms /// - Special trade conditions (e.g., block trades, opening auctions) Unknown, } /// TLOB (Time-Limited Order Book) analyzer pub struct TLOBAnalyzer { pub config: TLOBConfig, pub snapshots: BTreeMap>, pub order_flow: BTreeMap>, } /// TLOB snapshot for analysis #[derive(Debug, Clone)] pub struct TLOBSnapshot { pub timestamp: DateTime, pub bid_levels: Vec<(f64, f64)>, pub ask_levels: Vec<(f64, f64)>, pub mid_price: f64, pub weighted_mid: f64, pub imbalance: f64, } /// Order flow event for TLOB analysis #[derive(Debug, Clone)] pub struct OrderFlowEvent { pub timestamp: DateTime, pub event_type: OrderFlowEventType, pub price: f64, pub size: f64, pub side: String, } /// Order flow event types #[derive(Debug, Clone)] pub enum OrderFlowEventType { NewOrder, OrderCancel, OrderModify, Trade, MarketDataUpdate, } /// Temporal feature extractor for time-based market patterns. /// /// Extracts features related to time patterns, market sessions, and calendar /// effects that can be predictive in financial markets. These features help /// capture cyclical patterns and regime changes based on time of day, /// day of week, and other temporal factors. /// /// # Extracted Features /// /// ### Time of Day /// - Hour and minute components /// - Cyclical encoding using sine/cosine transforms /// - Market session indicators (pre-market, regular hours, after-hours) /// /// ### Calendar Effects /// - Day of week (Monday effect, Friday effect) /// - Weekend indicators /// - Month and day of month /// - Month-end and quarter-end effects /// /// ### Market Sessions (US Market) /// - **Pre-market**: 4:00 AM - 9:30 AM ET /// - **Regular Hours**: 9:30 AM - 4:00 PM ET /// - **After-hours**: 4:00 PM - 8:00 PM ET /// /// # Cyclical Encoding /// /// Time components are encoded using sine and cosine functions to capture /// cyclical nature and ensure smooth transitions (e.g., 23:59 to 00:00): /// /// ```text /// hour_sin = sin(hour * 2π / 24) /// hour_cos = cos(hour * 2π / 24) /// ``` /// /// # Applications /// /// - **Intraday Patterns**: Volume and volatility changes throughout the day /// - **Weekly Patterns**: Monday gaps, Friday positioning effects /// - **Calendar Effects**: Month-end rebalancing, quarterly window dressing /// - **Regime Detection**: Identifying different market regimes by time /// /// # Examples /// /// ```rust /// use data::features::TemporalFeatures; /// use chrono::{Utc, Timelike, Datelike}; /// /// let timestamp = Utc::now(); /// let features = TemporalFeatures::extract_features(timestamp); /// /// // Check for specific time-based conditions /// let is_market_open = features.get("is_regular_hours").unwrap_or(&0.0) > 0.5; /// let is_friday = features.get("weekday").unwrap_or(&0.0) == &4.0; /// let is_month_end = features.get("is_month_end").unwrap_or(&0.0) > 0.5; /// /// if is_market_open && is_friday { /// println!("Friday regular trading hours"); /// } /// ``` /// /// # Implementation Note /// /// This is a utility struct with static methods. All functionality is /// provided through the `extract_features` associated function. pub struct TemporalFeatures; /// Configuration for regime detector #[derive(Debug, Clone)] pub struct RegimeDetectorConfig { pub lookback_periods: usize, pub volatility_threshold: f64, pub trend_threshold: f64, pub correlation_threshold: f64, pub rebalance_frequency: usize, } /// Regime detection analyzer pub struct RegimeDetector { pub config: RegimeDetectorConfig, pub volatility_history: BTreeMap>, pub volume_history: BTreeMap>, pub price_history: BTreeMap>, pub correlation_matrix: HashMap>, } /// Configuration for portfolio analyzer #[derive(Debug, Clone)] pub struct PortfolioAnalyzerConfig { pub risk_free_rate: f64, pub target_return: f64, pub rebalance_threshold: f64, pub max_position_size: f64, pub diversification_target: usize, } /// Portfolio performance analyzer pub struct PortfolioAnalyzer { pub config: PortfolioAnalyzerConfig, pub positions: HashMap, pub pnl_history: VecDeque, pub risk_metrics: RiskMetrics, } /// Position information #[derive(Debug, Clone)] pub struct Position { pub symbol: String, pub quantity: f64, pub entry_price: f64, pub current_price: f64, pub entry_time: DateTime, pub last_update: DateTime, } /// P&L tracking point #[derive(Debug, Clone)] pub struct PnLPoint { pub timestamp: DateTime, pub realized_pnl: f64, pub unrealized_pnl: f64, pub total_pnl: f64, pub cumulative_pnl: f64, } /// Risk metrics #[derive(Debug, Clone)] pub struct RiskMetrics { pub var_95: f64, pub var_99: f64, pub expected_shortfall: f64, pub sharpe_ratio: f64, pub sortino_ratio: f64, pub max_drawdown: f64, pub volatility: f64, } impl TechnicalIndicators { /// Create new technical indicators calculator pub fn new(config: TechnicalIndicatorsConfig) -> Self { Self { config, price_data: BTreeMap::new(), volume_data: BTreeMap::new(), indicators: BTreeMap::new(), } } /// Update with new price data pub fn update_price(&mut self, symbol: &str, price_point: PricePoint) { let data = self .price_data .entry(symbol.to_string()) .or_insert_with(VecDeque::new); data.push_back(price_point.clone()); // Keep only required data based on largest period let max_period = self.config.ma_periods.iter().max().unwrap_or(&200); while data.len() > *max_period as usize { data.pop_front(); } // Update indicators self.update_indicators(symbol); } /// Calculate features for a symbol pub fn calculate_features(&self, symbol: &str) -> HashMap { let mut features = HashMap::new(); if let Some(indicators) = self.indicators.get(symbol) { // Simple Moving Averages for &period in &self.config.ma_periods { if let Some(&sma) = indicators.sma.get(&(period as u32)) { features.insert(format!("sma_{}", period), sma); } } // Exponential Moving Averages for &period in &self.config.ma_periods { if let Some(&ema) = indicators.ema.get(&(period as u32)) { features.insert(format!("ema_{}", period), ema); } } // RSI for &period in &self.config.rsi_periods { if let Some(&rsi) = indicators.rsi.get(&(period as u32)) { features.insert(format!("rsi_{}", period), rsi); } } // MACD features.insert("macd_line".to_string(), indicators.macd.macd_line); features.insert("macd_signal".to_string(), indicators.macd.signal_line); features.insert("macd_histogram".to_string(), indicators.macd.histogram); // Bollinger Bands for &period in &self.config.bollinger_periods { if let Some(bb) = indicators.bollinger.get(&(period as u32)) { features.insert(format!("bb_upper_{}", period), bb.upper_band); features.insert(format!("bb_middle_{}", period), bb.middle_band); features.insert(format!("bb_lower_{}", period), bb.lower_band); features.insert(format!("bb_bandwidth_{}", period), bb.bandwidth); features.insert(format!("bb_percent_b_{}", period), bb.percent_b); } } } features } /// Update all indicators for a symbol fn update_indicators(&mut self, symbol: &str) { // Get price data first to avoid borrowing conflicts let price_data = match self.price_data.get(symbol) { Some(data) => data.clone(), None => return, }; // Get configuration periods let ma_periods = self.config.ma_periods.clone(); let rsi_periods = self.config.rsi_periods.clone(); let bollinger_periods = self.config.bollinger_periods.clone(); // Get or create indicator state let indicator_state = self .indicators .entry(symbol.to_string()) .or_insert_with(|| IndicatorState { sma: HashMap::new(), ema: HashMap::new(), rsi: HashMap::new(), macd: MACDState { macd_line: 0.0, signal_line: 0.0, histogram: 0.0, fast_ema: 0.0, slow_ema: 0.0, signal_ema: 0.0, last_update: Utc::now(), }, bollinger: HashMap::new(), }); // Store current EMA values and MACD state for calculations let current_ema_values: HashMap = indicator_state.ema.clone(); let current_macd_state = indicator_state.macd.clone(); // Now we can perform calculations without borrowing conflicts // Update SMA for &period in &ma_periods { if let Some(sma) = Self::calculate_sma_static(&price_data, period as u32) { indicator_state.sma.insert(period as u32, sma); } } // Update EMA for &period in &ma_periods { if let Some(ema) = Self::calculate_ema_static( &price_data, period as u32, current_ema_values.get(&(period as u32)).copied(), ) { indicator_state.ema.insert(period as u32, ema); } } // Update RSI for &period in &rsi_periods { if let Some(rsi) = Self::calculate_rsi_static(&price_data, period as u32) { indicator_state.rsi.insert(period as u32, rsi); } } // Update MACD indicator_state.macd = Self::calculate_macd_static(&price_data, ¤t_macd_state); // Update Bollinger Bands for &period in &bollinger_periods { if let Some(bb) = Self::calculate_bollinger_bands_static(&price_data, period as u32) { indicator_state.bollinger.insert(period as u32, bb); } } } /// Calculate Simple Moving Average fn calculate_sma(&self, data: &VecDeque, period: u32) -> Option { if data.len() < period as usize { return None; } let sum: f64 = data .iter() .rev() .take(period as usize) .map(|p| p.close) .sum(); Some(sum / period as f64) } /// Calculate Exponential Moving Average fn calculate_ema( &self, data: &VecDeque, period: u32, prev_ema: Option, ) -> Option { if data.is_empty() { return None; } let current_price = data.back()?.close; let alpha = 2.0 / (period as f64 + 1.0); match prev_ema { Some(prev) => Some(alpha * current_price + (1.0 - alpha) * prev), None => Some(current_price), // First EMA value is the first price } } /// Calculate Relative Strength Index fn calculate_rsi(&self, data: &VecDeque, period: u32) -> Option { if data.len() < (period + 1) as usize { return None; } let mut gains = Vec::new(); let mut losses = Vec::new(); for window in data .iter() .rev() .take(period as usize + 1) .collect::>() .windows(2) { let change = window[0].close - window[1].close; if change > 0.0 { gains.push(change); losses.push(0.0); } else { gains.push(0.0); losses.push(-change); } } let avg_gain: f64 = gains.iter().sum::() / period as f64; let avg_loss: f64 = losses.iter().sum::() / period as f64; if avg_loss == 0.0 { return Some(100.0); } let rs = avg_gain / avg_loss; Some(100.0 - (100.0 / (1.0 + rs))) } /// Calculate MACD fn calculate_macd(&self, data: &VecDeque, prev_state: &MACDState) -> MACDState { if data.is_empty() { return prev_state.clone(); } let current_price = match data.back() { Some(price_point) => price_point.close, None => return prev_state.clone(), // Return previous state if data is unexpectedly empty }; let fast_alpha = 2.0 / (self.config.macd.fast_period as f64 + 1.0); let slow_alpha = 2.0 / (self.config.macd.slow_period as f64 + 1.0); let signal_alpha = 2.0 / (self.config.macd.signal_period as f64 + 1.0); let fast_ema = if prev_state.fast_ema == 0.0 { current_price } else { fast_alpha * current_price + (1.0 - fast_alpha) * prev_state.fast_ema }; let slow_ema = if prev_state.slow_ema == 0.0 { current_price } else { slow_alpha * current_price + (1.0 - slow_alpha) * prev_state.slow_ema }; let macd_line = fast_ema - slow_ema; let signal_line = if prev_state.signal_ema == 0.0 { macd_line } else { signal_alpha * macd_line + (1.0 - signal_alpha) * prev_state.signal_line }; let histogram = macd_line - signal_line; MACDState { macd_line, signal_line, histogram, fast_ema, slow_ema, signal_ema: signal_line, last_update: Utc::now(), } } /// Calculate Bollinger Bands fn calculate_bollinger_bands( &self, data: &VecDeque, period: u32, ) -> Option { if data.len() < period as usize { return None; } let prices: Vec = data .iter() .rev() .take(period as usize) .map(|p| p.close) .collect(); let mean = prices.iter().sum::() / period as f64; let variance = prices.iter().map(|&p| (p - mean).powi(2)).sum::() / period as f64; let std_dev = variance.sqrt(); let upper_band = mean + 2.0 * std_dev; let lower_band = mean - 2.0 * std_dev; let bandwidth = (upper_band - lower_band) / mean; let current_price = data.back()?.close; let percent_b = if upper_band != lower_band { (current_price - lower_band) / (upper_band - lower_band) } else { 0.5 }; Some(BollingerBandsState { upper_band, middle_band: mean, lower_band, bandwidth, percent_b, last_update: Utc::now(), }) } // Static methods to avoid borrowing conflicts /// Calculate Simple Moving Average (static version) fn calculate_sma_static(data: &VecDeque, period: u32) -> Option { if data.len() < period as usize { return None; } let sum: f64 = data .iter() .rev() .take(period as usize) .map(|p| p.close) .sum(); Some(sum / period as f64) } /// Calculate Exponential Moving Average (static version) fn calculate_ema_static( data: &VecDeque, period: u32, prev_ema: Option, ) -> Option { if data.is_empty() { return None; } let current_price = data.back()?.close; let alpha = 2.0 / (period as f64 + 1.0); match prev_ema { Some(prev) => Some(alpha * current_price + (1.0 - alpha) * prev), None => Some(current_price), // First EMA value is the current price } } /// Calculate RSI (static version) fn calculate_rsi_static(data: &VecDeque, period: u32) -> Option { if data.len() < (period + 1) as usize { return None; } let mut gains = 0.0; let mut losses = 0.0; // SAFETY: Loop bound ensures idx and prev_idx are valid indices. // We check data.len() >= period + 1 above, so: // - idx = data.len() - 1 - i where i < period, so idx >= 0 // - prev_idx = data.len() - 2 - i where i < period, so prev_idx >= 0 #[allow(clippy::indexing_slicing)] for i in 0..period { let idx = data.len() - 1 - i as usize; let prev_idx = data.len() - 2 - i as usize; let change = data[idx].close - data[prev_idx].close; if change > 0.0 { gains += change; } else { losses += -change; } } let avg_gain = gains / period as f64; let avg_loss = losses / period as f64; if avg_loss == 0.0 { return Some(100.0); } let rs = avg_gain / avg_loss; Some(100.0 - (100.0 / (1.0 + rs))) } /// Calculate MACD (static version) fn calculate_macd_static(data: &VecDeque, prev_state: &MACDState) -> MACDState { if data.is_empty() { return prev_state.clone(); } let current_price = match data.back() { Some(point) => point.close, None => return prev_state.clone(), }; // Use fixed MACD parameters let fast_alpha = 2.0 / 13.0; // 12-day EMA let slow_alpha = 2.0 / 27.0; // 26-day EMA let signal_alpha = 2.0 / 10.0; // 9-day EMA let fast_ema = if prev_state.fast_ema == 0.0 { current_price } else { fast_alpha * current_price + (1.0 - fast_alpha) * prev_state.fast_ema }; let slow_ema = if prev_state.slow_ema == 0.0 { current_price } else { slow_alpha * current_price + (1.0 - slow_alpha) * prev_state.slow_ema }; let macd_line = fast_ema - slow_ema; let signal_line = if prev_state.signal_line == 0.0 { macd_line } else { signal_alpha * macd_line + (1.0 - signal_alpha) * prev_state.signal_line }; let histogram = macd_line - signal_line; MACDState { macd_line, signal_line, histogram, fast_ema, slow_ema, signal_ema: signal_line, last_update: Utc::now(), } } /// Calculate Bollinger Bands (static version) fn calculate_bollinger_bands_static( data: &VecDeque, period: u32, ) -> Option { if data.len() < period as usize { return None; } let prices: Vec = data .iter() .rev() .take(period as usize) .map(|p| p.close) .collect(); let mean = prices.iter().sum::() / period as f64; let variance = prices.iter().map(|&p| (p - mean).powi(2)).sum::() / period as f64; let std_dev = variance.sqrt(); let upper_band = mean + 2.0 * std_dev; let lower_band = mean - 2.0 * std_dev; let bandwidth = (upper_band - lower_band) / mean; let current_price = data.back()?.close; let percent_b = if upper_band != lower_band { (current_price - lower_band) / (upper_band - lower_band) } else { 0.5 }; Some(BollingerBandsState { upper_band, middle_band: mean, lower_band, bandwidth, percent_b, last_update: Utc::now(), }) } } impl MicrostructureAnalyzer { /// Create new microstructure analyzer pub fn new(config: MicrostructureConfig) -> Self { Self { config, order_books: HashMap::new(), trade_data: BTreeMap::new(), quote_data: BTreeMap::new(), } } /// Update with new quote data pub fn update_quote(&mut self, symbol: &str, quote: QuoteData) { let data = self .quote_data .entry(symbol.to_string()) .or_insert_with(VecDeque::new); data.push_back(quote); // Keep only recent data while data.len() > 1000 { data.pop_front(); } } /// Update with new trade data pub fn update_trade(&mut self, symbol: &str, trade: TradeData) { let data = self .trade_data .entry(symbol.to_string()) .or_insert_with(VecDeque::new); data.push_back(trade); // Keep only recent data while data.len() > 1000 { data.pop_front(); } } /// Calculate microstructure features pub fn calculate_features(&self, symbol: &str) -> HashMap { let mut features = HashMap::new(); // Bid-ask spread features if self.config.bid_ask_spread { if let Some(spread) = self.calculate_bid_ask_spread(symbol) { features.insert("bid_ask_spread".to_string(), spread.bid_ask_spread); features.insert("relative_spread".to_string(), spread.relative_spread); features.insert("effective_spread".to_string(), spread.effective_spread); } } // Volume imbalance if self.config.volume_imbalance { if let Some(imbalance) = self.calculate_volume_imbalance(symbol) { features.insert("volume_imbalance".to_string(), imbalance); } } // Price impact if self.config.price_impact { if let Some(impact) = self.calculate_price_impact(symbol) { features.insert("price_impact".to_string(), impact); } } // Kyle's lambda if self.config.kyle_lambda { if let Some(lambda) = self.calculate_kyle_lambda(symbol) { features.insert("kyle_lambda".to_string(), lambda); } } // Amihud illiquidity ratio if self.config.amihud_ratio { if let Some(ratio) = self.calculate_amihud_ratio(symbol) { features.insert("amihud_ratio".to_string(), ratio); } } // Roll spread (using bid_ask_spread field) if self.config.bid_ask_spread { if let Some(roll) = self.calculate_roll_spread(symbol) { features.insert("roll_spread".to_string(), roll); } } features } /// Calculate bid-ask spread metrics fn calculate_bid_ask_spread(&self, symbol: &str) -> Option { let quote_data = self.quote_data.get(symbol)?; let latest_quote = quote_data.back()?; let bid_ask_spread = latest_quote.ask_price - latest_quote.bid_price; let mid_price = (latest_quote.ask_price + latest_quote.bid_price) / 2.0; let relative_spread = bid_ask_spread / mid_price; Some(SpreadMetrics { bid_ask_spread, relative_spread, effective_spread: bid_ask_spread * 0.5, realized_spread: bid_ask_spread * 0.3, price_impact: bid_ask_spread * 0.2, }) } /// Calculate volume imbalance fn calculate_volume_imbalance(&self, symbol: &str) -> Option { let quote_data = self.quote_data.get(symbol)?; let latest_quote = quote_data.back()?; let total_volume = latest_quote.bid_size + latest_quote.ask_size; if total_volume == 0.0 { return Some(0.0); } Some((latest_quote.bid_size - latest_quote.ask_size) / total_volume) } /// Calculate price impact fn calculate_price_impact(&self, symbol: &str) -> Option { let trade_data = self.trade_data.get(symbol)?; if trade_data.len() < 2 { return None; } let recent_trades: Vec<&TradeData> = trade_data.iter().rev().take(10).collect(); let price_changes: Vec = recent_trades .windows(2) .map(|w| w[0].price - w[1].price) .collect(); if price_changes.is_empty() { return None; } let avg_price_change = price_changes.iter().sum::() / price_changes.len() as f64; Some(avg_price_change) } /// Calculate Kyle's lambda (price impact parameter). /// /// Kyle's lambda is the regression coefficient of price change on signed order flow: /// lambda = cov(delta_price, signed_volume) / var(signed_volume) /// /// Uses a rolling window of recent trades. Signed volume is determined by the tick rule: /// sign = sign(price_change) applied to volume. fn calculate_kyle_lambda(&self, symbol: &str) -> Option { let trade_data = self.trade_data.get(symbol)?; let quote_data = self.quote_data.get(symbol)?; if trade_data.len() < 20 || quote_data.len() < 10 { return None; } // Use the most recent 100 trades (or all available if fewer) let window_size = 100.min(trade_data.len()); let recent_trades: Vec<&TradeData> = trade_data.iter().rev().take(window_size).collect(); // Need at least 20 consecutive trades for meaningful regression if recent_trades.len() < 20 { return None; } // Compute price changes and signed volumes using tick rule let mut price_changes = Vec::with_capacity(recent_trades.len() - 1); let mut signed_volumes = Vec::with_capacity(recent_trades.len() - 1); for window in recent_trades.windows(2) { let current = window[0]; let previous = window[1]; let delta_price = current.price - previous.price; // Tick rule: sign volume by the direction of the price change let sign = if delta_price > 0.0 { 1.0 } else if delta_price < 0.0 { -1.0 } else { // No price change: use the trade's classified direction if available match current.direction { TradeDirection::Buy => 1.0, TradeDirection::Sell => -1.0, TradeDirection::Unknown => 0.0, } }; let signed_vol = current.size * sign; price_changes.push(delta_price); signed_volumes.push(signed_vol); } let n = price_changes.len() as f64; if n < 2.0 { return None; } // Compute means let mean_dp = price_changes.iter().sum::() / n; let mean_sv = signed_volumes.iter().sum::() / n; // Compute covariance and variance let mut cov = 0.0; let mut var_sv = 0.0; for i in 0..price_changes.len() { let dp_diff = price_changes[i] - mean_dp; let sv_diff = signed_volumes[i] - mean_sv; cov += dp_diff * sv_diff; var_sv += sv_diff * sv_diff; } cov /= n; var_sv /= n; // Avoid division by zero (no variance in signed volume) if var_sv.abs() < 1e-18 { return None; } let lambda = cov / var_sv; // Kyle's lambda should be non-negative (higher order flow -> higher price impact) // A negative value indicates the model is not well-specified for this data window if lambda < 0.0 { None } else { Some(lambda) } } /// Calculate Amihud illiquidity ratio fn calculate_amihud_ratio(&self, symbol: &str) -> Option { let trade_data = self.trade_data.get(symbol)?; if trade_data.len() < 2 { return None; } let recent_trades: Vec<&TradeData> = trade_data.iter().rev().take(20).collect(); let mut total_ratio = 0.0; let mut count = 0; for window in recent_trades.windows(2) { let price_change = (window[0].price - window[1].price).abs(); let volume = window[0].size; if volume > 0.0 { total_ratio += price_change / volume; count += 1; } } if count > 0 { Some(total_ratio / count as f64) } else { None } } /// Calculate Roll spread estimator fn calculate_roll_spread(&self, symbol: &str) -> Option { let trade_data = self.trade_data.get(symbol)?; if trade_data.len() < 3 { return None; } let recent_trades: Vec<&TradeData> = trade_data.iter().rev().take(50).collect(); let price_changes: Vec = recent_trades .windows(2) .map(|w| w[0].price - w[1].price) .collect(); if price_changes.len() < 2 { return None; } // Calculate serial covariance let mean_change = price_changes.iter().sum::() / price_changes.len() as f64; let covariance: f64 = price_changes .windows(2) .map(|w| (w[0] - mean_change) * (w[1] - mean_change)) .sum::() / (price_changes.len() - 1) as f64; Some(2.0 * (-covariance).max(0.0).sqrt()) } } /// Comprehensive bid-ask spread metrics for transaction cost analysis. /// /// Provides multiple representations of the bid-ask spread to support /// different analysis requirements and to normalize spreads across /// different price levels and market conditions. /// /// # Spread Representations /// /// - **Absolute**: Raw price difference (Ask - Bid) /// - **Percentage**: Relative to mid-price ((Ask - Bid) / Mid-price) /// - **Basis Points**: Percentage × 10,000 for easier interpretation /// /// # Applications /// /// - **Transaction Cost Analysis**: Estimating cost of immediate execution /// - **Liquidity Assessment**: Lower spreads indicate higher liquidity /// - **Market Comparison**: Normalized spreads enable cross-asset comparison /// - **Regime Detection**: Spread changes indicate market stress or calm /// /// # Interpretation Guidelines /// /// - **Tight Spreads** (< 5 bps): Highly liquid, low transaction costs /// - **Normal Spreads** (5-20 bps): Standard liquidity for most assets /// - **Wide Spreads** (> 20 bps): Lower liquidity, higher transaction costs /// - **Very Wide Spreads** (> 100 bps): Illiquid or stressed market conditions /// /// # Examples /// /// ```rust /// use data::features::SpreadMetrics; /// /// let spread = SpreadMetrics { /// absolute: 0.05, // 5 cent spread /// percentage: 0.0003, // 0.03% of mid-price /// basis_points: 3.0, // 3 basis points /// }; /// /// // Classify liquidity based on spread /// let liquidity_tier = match spread.basis_points { /// bp if bp < 5.0 => "Highly Liquid", /// bp if bp < 20.0 => "Liquid", /// bp if bp < 50.0 => "Moderately Liquid", /// _ => "Illiquid", /// }; /// ``` #[derive(Debug, Clone)] pub struct SpreadMetrics { pub bid_ask_spread: f64, pub relative_spread: f64, pub effective_spread: f64, pub realized_spread: f64, pub price_impact: f64, } impl TemporalFeatures { /// Extract temporal features from timestamp pub fn extract_features(timestamp: DateTime) -> HashMap { let mut features = HashMap::new(); // Convert UTC to EST (UTC-5) for market hour calculations // Note: This doesn't account for daylight saving time, assumes EST year-round use chrono::FixedOffset; // SAFETY: 5*3600 = 18000 seconds is always a valid UTC offset #[allow(clippy::unwrap_used)] let est_offset = FixedOffset::west_opt(5 * 3600).unwrap(); let est_time = timestamp.with_timezone(&est_offset); // Time of day features (using EST for market hours) let hour = est_time.hour() as f64; let minute = est_time.minute() as f64; features.insert("hour".to_string(), hour); features.insert("minute".to_string(), minute); features.insert( "hour_sin".to_string(), (hour * 2.0 * std::f64::consts::PI / 24.0).sin(), ); features.insert( "hour_cos".to_string(), (hour * 2.0 * std::f64::consts::PI / 24.0).cos(), ); // Day of week features let weekday = timestamp.weekday().num_days_from_monday() as f64; features.insert("weekday".to_string(), weekday); features.insert( "is_weekend".to_string(), if weekday >= 5.0 { 1.0 } else { 0.0 }, ); // Market session features (assuming US market hours) let is_premarket = hour < 9.0 || (hour == 9.0 && minute < 30.0); let is_regular_hours = (hour > 9.0 || (hour == 9.0 && minute >= 30.0)) && hour < 16.0; let is_aftermarket = hour >= 16.0 && hour < 20.0; features.insert( "is_premarket".to_string(), if is_premarket { 1.0 } else { 0.0 }, ); features.insert( "is_regular_hours".to_string(), if is_regular_hours { 1.0 } else { 0.0 }, ); features.insert( "is_aftermarket".to_string(), if is_aftermarket { 1.0 } else { 0.0 }, ); // Month and day features let month = timestamp.month() as f64; let day = timestamp.day() as f64; features.insert("month".to_string(), month); features.insert("day".to_string(), day); features.insert( "is_month_end".to_string(), if day >= 28.0 { 1.0 } else { 0.0 }, ); features.insert( "is_quarter_end".to_string(), if month % 3.0 == 0.0 && day >= 28.0 { 1.0 } else { 0.0 }, ); features } } impl TLOBAnalyzer { pub fn new(config: TLOBConfig) -> Self { Self { config, snapshots: BTreeMap::new(), order_flow: BTreeMap::new(), } } } impl RegimeDetector { pub fn new(config: RegimeDetectorConfig) -> Self { Self { config, volatility_history: BTreeMap::new(), volume_history: BTreeMap::new(), price_history: BTreeMap::new(), correlation_matrix: HashMap::new(), } } } impl PortfolioAnalyzer { pub fn new(config: PortfolioAnalyzerConfig) -> Self { Self { config, positions: HashMap::new(), pnl_history: VecDeque::new(), risk_metrics: RiskMetrics { var_95: 0.0, var_99: 0.0, expected_shortfall: 0.0, sharpe_ratio: 0.0, sortino_ratio: 0.0, max_drawdown: 0.0, volatility: 0.0, }, } } } #[cfg(test)] #[allow(clippy::get_unwrap)] mod tests { use super::*; #[test] fn test_technical_indicators_creation() { let config = TechnicalIndicatorsConfig { enable_moving_averages: true, enable_momentum: true, enable_volatility: true, window_sizes: vec![10, 20], ma_periods: vec![10, 20], rsi_periods: vec![14], bollinger_periods: vec![20], macd: config::data_config::DataMACDConfig { fast_period: 12, slow_period: 26, signal_period: 9, enabled: true, }, }; let indicators = TechnicalIndicators::new(config); assert!(indicators.price_data.is_empty()); assert!(indicators.indicators.is_empty()); } #[test] fn test_temporal_features() { let timestamp = Utc::now(); let features = TemporalFeatures::extract_features(timestamp); assert!(features.contains_key("hour")); assert!(features.contains_key("weekday")); assert!(features.contains_key("is_regular_hours")); } #[test] fn test_microstructure_analyzer() { let config = MicrostructureConfig { enable_bid_ask_spread: true, enable_order_flow: true, tick_size: 0.01, lot_size: 100.0, bid_ask_spread: true, volume_imbalance: true, price_impact: true, kyle_lambda: false, amihud_ratio: false, }; let analyzer = MicrostructureAnalyzer::new(config); assert!(analyzer.order_books.is_empty()); assert!(analyzer.trade_data.is_empty()); } #[test] fn test_feature_vector_creation() { let mut features = HashMap::new(); features.insert("price".to_string(), 100.0); features.insert("volume".to_string(), 1000.0); let mut feature_categories = HashMap::new(); feature_categories.insert("price".to_string(), FeatureCategory::Price); feature_categories.insert("volume".to_string(), FeatureCategory::Volume); let metadata = FeatureMetadata { symbol: "AAPL".to_string(), timestamp: Utc::now(), feature_count: 2, categories: vec![FeatureCategory::Price, FeatureCategory::Volume], feature_descriptions: HashMap::new(), feature_categories, quality_indicators: HashMap::new(), }; let vector = FeatureVector { timestamp: Utc::now(), symbol: "AAPL".to_string(), features, metadata: metadata.clone(), }; assert_eq!(vector.features.len(), 2); assert_eq!(vector.metadata.feature_count, 2); assert_eq!(vector.metadata.symbol, "AAPL"); } #[test] fn test_technical_indicators_update() { let config = TechnicalIndicatorsConfig { enable_moving_averages: true, enable_momentum: true, enable_volatility: true, window_sizes: vec![5], ma_periods: vec![5], rsi_periods: vec![14], bollinger_periods: vec![20], macd: config::data_config::DataMACDConfig { fast_period: 12, slow_period: 26, signal_period: 9, enabled: true, }, }; let mut indicators = TechnicalIndicators::new(config); // Add some price points for i in 0..10 { let price = PricePoint { timestamp: Utc::now(), open: 100.0 + i as f64, high: 102.0 + i as f64, low: 99.0 + i as f64, close: 101.0 + i as f64, }; indicators.update_price("AAPL", price); } // price_data is a HashMap> // We expect 1 symbol ("AAPL") // Data is trimmed to max_period (5 in this test's config) assert_eq!(indicators.price_data.len(), 1); assert_eq!(indicators.price_data.get("AAPL").unwrap().len(), 5); } #[test] fn test_volume_point_validation() { let volume = VolumePoint { timestamp: Utc::now(), volume: 1000.0, volume_weighted_price: 100.5, buy_volume: 600.0, sell_volume: 400.0, }; assert_eq!(volume.volume, 1000.0); assert_eq!(volume.buy_volume, 600.0); assert!(volume.buy_volume + volume.sell_volume == 1000.0); } #[test] fn test_macd_state() { let state = MACDState { macd_line: 2.5, signal_line: 2.0, histogram: 0.5, fast_ema: 102.0, slow_ema: 99.5, signal_ema: 2.0, last_update: Utc::now(), }; assert_eq!(state.macd_line - state.signal_line, state.histogram); } #[test] fn test_bollinger_bands_state() { let state = BollingerBandsState { upper_band: 105.0, middle_band: 100.0, lower_band: 95.0, bandwidth: 10.0, percent_b: 0.5, last_update: Utc::now(), }; assert!(state.upper_band > state.middle_band); assert!(state.middle_band > state.lower_band); assert_eq!(state.bandwidth, 10.0); } #[test] fn test_order_book_state() { let state = OrderBookState { timestamp: Utc::now(), best_bid: 99.5, best_ask: 100.5, bid_size: 1000.0, ask_size: 800.0, spread: 1.0, mid_price: 100.0, }; assert_eq!(state.best_ask - state.best_bid, state.spread); assert_eq!((state.best_bid + state.best_ask) / 2.0, state.mid_price); } #[test] fn test_trade_data() { let trade = TradeData { timestamp: Utc::now(), price: 100.0, size: 100.0, direction: TradeDirection::Buy, conditions: vec!["RegularSale".to_string()], }; assert_eq!(trade.price, 100.0); assert_eq!(trade.size, 100.0); assert!(matches!(trade.direction, TradeDirection::Buy)); } #[test] fn test_quote_data() { let quote = QuoteData { timestamp: Utc::now(), bid_price: 99.5, ask_price: 100.5, bid_size: 1000.0, ask_size: 800.0, exchange: "NYSE".to_string(), }; assert!(quote.ask_price > quote.bid_price); assert_eq!(quote.exchange, "NYSE"); } #[test] fn test_tlob_analyzer_creation() { let config = TLOBConfig { depth_levels: 10, enable_imbalance: true, enable_pressure: true, window_size: 100, }; let analyzer = TLOBAnalyzer::new(config); assert!(analyzer.snapshots.is_empty()); } #[test] fn test_tlob_snapshot() { let snapshot = TLOBSnapshot { timestamp: Utc::now(), bid_levels: vec![(99.5, 1000.0), (99.0, 1500.0)], ask_levels: vec![(100.5, 800.0), (101.0, 1200.0)], mid_price: 100.0, weighted_mid: 99.9, imbalance: 0.2, }; assert_eq!(snapshot.bid_levels.len(), 2); assert_eq!(snapshot.ask_levels.len(), 2); assert_eq!(snapshot.mid_price, 100.0); } #[test] fn test_order_flow_event() { let event = OrderFlowEvent { timestamp: Utc::now(), event_type: OrderFlowEventType::NewOrder, price: 100.0, size: 100.0, side: "buy".to_string(), }; assert!(matches!(event.event_type, OrderFlowEventType::NewOrder)); assert_eq!(event.side, "buy"); } #[test] fn test_temporal_features_market_hours() { use chrono::TimeZone; // Test regular hours (10:00 AM EST = 15:00 UTC) let regular_hours = Utc.with_ymd_and_hms(2024, 1, 15, 15, 0, 0).unwrap(); let features = TemporalFeatures::extract_features(regular_hours); assert_eq!(features.get("is_regular_hours"), Some(&1.0)); assert_eq!(features.get("is_premarket"), Some(&0.0)); assert_eq!(features.get("is_aftermarket"), Some(&0.0)); } #[test] fn test_temporal_features_premarket() { use chrono::TimeZone; // Test premarket (8:00 AM EST = 13:00 UTC) let premarket = Utc.with_ymd_and_hms(2024, 1, 15, 13, 0, 0).unwrap(); let features = TemporalFeatures::extract_features(premarket); assert_eq!(features.get("is_premarket"), Some(&1.0)); assert_eq!(features.get("is_regular_hours"), Some(&0.0)); } #[test] fn test_temporal_features_quarter_end() { use chrono::TimeZone; // Test quarter end (March 31) let quarter_end = Utc.with_ymd_and_hms(2024, 3, 31, 12, 0, 0).unwrap(); let features = TemporalFeatures::extract_features(quarter_end); assert_eq!(features.get("is_quarter_end"), Some(&1.0)); assert_eq!(features.get("is_month_end"), Some(&1.0)); } #[test] fn test_regime_detector_creation() { let config = RegimeDetectorConfig { lookback_periods: 50, volatility_threshold: 0.02, trend_threshold: 0.01, correlation_threshold: 0.7, rebalance_frequency: 100, }; let detector = RegimeDetector::new(config); assert!(detector.price_history.is_empty()); } #[test] fn test_portfolio_analyzer_creation() { let config = PortfolioAnalyzerConfig { risk_free_rate: 0.03, target_return: 0.10, rebalance_threshold: 0.05, max_position_size: 0.20, diversification_target: 10, }; let analyzer = PortfolioAnalyzer::new(config); assert!(analyzer.positions.is_empty()); } #[test] fn test_position_creation() { let position = Position { symbol: "AAPL".to_string(), quantity: 100.0, entry_price: 150.0, current_price: 155.0, entry_time: Utc::now(), last_update: Utc::now(), }; let pnl = (position.current_price - position.entry_price) * position.quantity; assert_eq!(pnl, 500.0); // (155 - 150) * 100 } #[test] fn test_pnl_point() { let pnl = PnLPoint { timestamp: Utc::now(), realized_pnl: 1000.0, unrealized_pnl: 500.0, total_pnl: 1500.0, cumulative_pnl: 5000.0, }; assert_eq!(pnl.realized_pnl + pnl.unrealized_pnl, pnl.total_pnl); } #[test] fn test_risk_metrics() { let metrics = RiskMetrics { var_95: 10000.0, var_99: 15000.0, expected_shortfall: 18000.0, sharpe_ratio: 1.5, sortino_ratio: 2.0, max_drawdown: 0.15, volatility: 0.02, }; assert!(metrics.var_99 > metrics.var_95); assert!(metrics.expected_shortfall > metrics.var_99); assert!(metrics.sortino_ratio > metrics.sharpe_ratio); } #[test] fn test_spread_metrics() { let metrics = SpreadMetrics { bid_ask_spread: 0.01, relative_spread: 0.0001, effective_spread: 0.005, realized_spread: 0.003, price_impact: 0.002, }; assert!(metrics.bid_ask_spread > metrics.effective_spread); assert!(metrics.effective_spread > metrics.realized_spread); } #[test] fn test_feature_category_ordering() { assert!(FeatureCategory::Price < FeatureCategory::Volume); assert!(FeatureCategory::TechnicalIndicator < FeatureCategory::Microstructure); } }