//! Market microstructure analysis module //! //! This module provides comprehensive analysis of market microstructure data, //! including order book analysis, trade flow analysis, price impact modeling, //! and microstructure feature extraction for adaptive trading strategies. use anyhow::Result; use chrono::Duration; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, VecDeque}; use tracing::{debug, info, warn}; // Add missing core types // REMOVED: Add ML types - compilation issues // use ml::prelude::*; // REMOVED: Add data types - compilation issues // use data::*; use super::config::MicrostructureConfig; // REMOVED: Import VPIN calculator from ml crate - compilation issues // use ml::microstructure::{MarketDataUpdate, TradeDirection, VPINCalculator, VPINConfig}; // Local stub definitions to replace ml crate types /// Volume-Synchronized Probability of Informed Trading (VPIN) Calculator /// /// VPIN is a measure of order flow toxicity and the probability that informed /// traders are present in the market. Higher VPIN values indicate higher /// probability of adverse selection for market makers. #[derive(Debug, Clone)] pub struct VPINCalculator {} impl VPINCalculator { /// Create a new VPIN calculator with the given configuration /// /// # Arguments /// /// * `config` - VPIN calculation parameters pub fn new(_config: VPINConfig) -> Self { Self {} } /// Update VPIN calculation with new market data /// /// # Arguments /// /// * `_update` - Market data update containing trade and quote information /// /// # Returns /// /// Result indicating success or failure of the update pub fn update(&mut self, _update: &MarketDataUpdate) -> Result<(), String> { Ok(()) } /// Get current VPIN metrics and analysis results /// /// # Returns /// /// Current VPIN metrics including toxicity scores and confidence levels pub fn get_result(&self) -> VPINMetrics { VPINMetrics { vpin: 0.3_f64, confidence: 0.8_f64, order_flow_imbalance: 0.1_f64, toxicity_score: 0.2_f64, is_toxic: false, bucket_count: 25_usize, current_bucket_fill: 0.7_f64, } } /// Check if current order flow is considered toxic /// /// # Returns /// /// True if order flow toxicity exceeds threshold, false otherwise pub fn is_toxic(&self) -> bool { false } } /// Configuration parameters for VPIN calculation /// /// Controls the behavior of the VPIN calculator including volume buckets, /// time windows, and toxicity thresholds. #[derive(Debug, Clone)] pub struct VPINConfig { /// Number of volume buckets in the rolling window pub window_size: usize, /// Size of each volume bucket for VPIN calculation pub volume_bucket_size: f64, /// Volume threshold for bucket completion pub bucket_volume: u64, /// Total number of buckets in the rolling window /// Number of buckets used in VPIN calculation rolling window /// This determines the size of the rolling window for VPIN computation. /// More buckets provide more stable but less responsive VPIN values. pub bucket_count: usize, /// Threshold above which order flow is considered toxic pub toxicity_threshold: u64, /// Maximum age of data in microseconds before expiration pub max_age_us: u64, } /// Market data update containing trade and quote information /// /// Represents a single market data event including trade execution, /// quote updates, and order book changes for VPIN analysis. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MarketDataUpdate { /// Trading symbol or instrument identifier pub symbol: String, /// Event timestamp in microseconds since epoch pub timestamp: u64, /// Trade price in scaled integer format (e.g., price * 10000) pub price: i64, /// Trade volume or quantity pub volume: u64, /// Trade direction (buy/sell/unknown) pub side: TradeDirection, /// Best bid price in scaled integer format pub bid: i64, /// Best ask price in scaled integer format pub ask: i64, /// Size available at best bid pub bid_size: u64, /// Size available at best ask pub ask_size: u64, /// Optional refined trade direction classification pub direction: Option, } /// Trade direction classification for order flow analysis /// /// Represents the aggressive side of a trade, indicating whether /// the trade was initiated by a buyer or seller. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum TradeDirection { /// Buyer-initiated trade (aggressive buy order) Buy, /// Seller-initiated trade (aggressive sell order) Sell, /// Trade direction could not be determined Unknown, } /// VPIN calculation results and toxicity metrics /// /// Contains the calculated VPIN value along with additional metrics /// for assessing order flow toxicity and market microstructure quality. #[derive(Debug, Clone)] pub struct VPINMetrics { /// VPIN value (0-1 scale, higher indicates more toxic flow) pub vpin: f64, /// Confidence in the VPIN calculation (0-1 scale) pub confidence: f64, /// Order flow imbalance metric (-1 to 1, negative=sell pressure) pub order_flow_imbalance: f64, /// Overall toxicity score (0-1 scale) pub toxicity_score: f64, /// Whether current flow exceeds toxicity threshold pub is_toxic: bool, /// Number of buckets in the VPIN calculation window pub bucket_count: usize, /// Fill percentage of current volume bucket (0-1) pub current_bucket_fill: f64, } // Struct definitions provided above replace ml crate types /// Market microstructure analyzer /// /// Processes order book data, trade data, and market events to extract /// microstructure features and signals for trading strategies. pub struct MicrostructureAnalyzer { /// Configuration parameters (stored for potential reconfiguration/debugging) #[allow(dead_code)] config: MicrostructureConfig, /// Order book state tracker order_book: OrderBookTracker, /// Trade flow analyzer trade_flow: TradeFlowAnalyzer, /// Price impact model price_impact: PriceImpactModel, /// Feature extraction engine feature_extractor: FeatureExtractor, /// VPIN calculator for order flow toxicity vpin_calculator: VPINCalculator, } impl std::fmt::Debug for MicrostructureAnalyzer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("MicrostructureAnalyzer") .field("order_book", &self.order_book) .field("trade_flow", &self.trade_flow) .field("price_impact", &self.price_impact) .field("feature_extractor", &self.feature_extractor) .field("vpin_calculator", &"") .finish() } } /// Order book state and analysis #[derive(Debug, Clone)] pub struct OrderBookTracker { /// Current bid levels bids: VecDeque, /// Current ask levels asks: VecDeque, /// Maximum depth to track max_depth: usize, /// Last update timestamp last_update: chrono::DateTime, /// Order book imbalance history imbalance_history: VecDeque, } /// Order book level #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OrderLevel { /// Price level pub price: f64, /// Total quantity at this level pub quantity: f64, /// Number of orders at this level pub order_count: u32, /// Timestamp of last update pub timestamp: chrono::DateTime, } /// Trade flow analysis #[derive(Debug, Clone)] pub struct TradeFlowAnalyzer { /// Recent trades recent_trades: VecDeque, /// Trade size buckets size_buckets: Vec, /// VWAP calculator vwap_calculator: VWAPCalculator, /// Trade sign classifier trade_classifier: TradeSignClassifier, } /// Individual trade record #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Trade { /// Trade price pub price: f64, /// Trade quantity pub quantity: f64, /// Trade timestamp pub timestamp: chrono::DateTime, /// Trade side (buy/sell pressure) pub side: TradeSide, /// Trade size category pub size_category: TradeSizeCategory, } /// Trade side classification #[derive(Debug, Clone, Serialize, Deserialize)] pub enum TradeSide { /// Buyer-initiated trade Buy, /// Seller-initiated trade Sell, /// Undetermined direction Unknown, } /// Trade size categories #[derive(Debug, Clone, Serialize, Deserialize)] pub enum TradeSizeCategory { /// Small retail trade Small, /// Medium institutional trade Medium, /// Large block trade Large, /// Very large whale trade VeryLarge, } /// Price impact modeling #[derive(Debug, Clone)] pub struct PriceImpactModel { /// Recent price impact measurements impact_history: VecDeque, /// Linear impact coefficient linear_coefficient: f64, /// Square root impact coefficient sqrt_coefficient: f64, } /// Price impact measurement #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PriceImpactMeasurement { /// Trade size pub trade_size: f64, /// Measured price impact pub impact: f64, /// Time since trade pub time_elapsed: Duration, /// Market conditions during trade pub market_state: MarketState, } /// Market state for impact analysis #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MarketState { /// Bid-ask spread pub spread: f64, /// Market volatility pub volatility: f64, /// Trading volume pub volume: f64, /// Order book depth pub depth: f64, } /// Feature extraction from microstructure data #[derive(Debug, Clone)] pub struct FeatureExtractor { /// Features to extract enabled_features: Vec, /// Feature history for rolling calculations #[allow(dead_code)] feature_history: HashMap>, /// Calculation windows #[allow(dead_code)] windows: Vec, } /// Available microstructure features #[derive(Debug, Clone, Serialize, Deserialize)] pub enum MicrostructureFeature { /// Bid-ask spread (absolute and relative) BidAskSpread, /// Order book imbalance OrderBookImbalance, /// Trade sign and buy/sell pressure TradeSign, /// Volume profile and distribution VolumeProfile, /// Price impact measurements PriceImpact, /// Microstructure noise estimation MicrostructureNoise, /// Order flow toxicity OrderFlowToxicity, /// Market depth and liquidity MarketDepth, } /// VWAP calculation engine #[derive(Debug, Clone)] pub struct VWAPCalculator { /// Price-volume pairs price_volume_pairs: VecDeque<(f64, f64, chrono::DateTime)>, /// Calculation window window_duration: Duration, } /// Trade sign classification #[derive(Debug, Clone)] pub struct TradeSignClassifier { /// Classification method method: TradeSignMethod, } /// Quote data for trade classification #[derive(Debug, Clone)] pub struct Quote { /// Best bid price pub bid: f64, /// Best ask price pub ask: f64, /// Timestamp pub timestamp: chrono::DateTime, } /// Trade sign classification methods #[derive(Debug, Clone)] pub enum TradeSignMethod { /// Quote-based classification QuoteBased, /// Tick rule TickRule, /// Lee-Ready algorithm LeeReady, } /// Extracted microstructure features #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MicrostructureFeatures { /// Feature values by name pub features: HashMap, /// Feature timestamp pub timestamp: chrono::DateTime, /// Market conditions pub market_state: MarketState, /// Data quality indicators pub quality_indicators: QualityIndicators, } /// Data quality indicators #[derive(Debug, Clone, Serialize, Deserialize)] pub struct QualityIndicators { /// Order book completeness (0-1) pub book_completeness: f64, /// Trade data completeness (0-1) pub trade_completeness: f64, /// Data latency (milliseconds) pub data_latency_ms: f64, /// Missing data points pub missing_data_points: u32, } impl MicrostructureAnalyzer { /// Create a new microstructure analyzer /// /// # Arguments /// /// * `config` - Microstructure analysis configuration /// /// # Returns /// /// A new `MicrostructureAnalyzer` instance pub fn new(config: MicrostructureConfig) -> Result { info!( "Initializing microstructure analyzer with depth: {}", config.book_depth ); let order_book = OrderBookTracker::new(config.book_depth); let trade_flow = TradeFlowAnalyzer::new(&config.trade_size_buckets); let price_impact = PriceImpactModel::new(); // Convert string features to MicrostructureFeature enum let microstructure_features: Vec = config .features .iter() .filter_map(|f| match f.as_str() { "BidAskSpread" => Some(MicrostructureFeature::BidAskSpread), "OrderBookImbalance" => Some(MicrostructureFeature::OrderBookImbalance), "TradeSign" => Some(MicrostructureFeature::TradeSign), "VolumeProfile" => Some(MicrostructureFeature::VolumeProfile), "PriceImpact" => Some(MicrostructureFeature::PriceImpact), "MicrostructureNoise" => Some(MicrostructureFeature::MicrostructureNoise), "OrderFlowToxicity" => Some(MicrostructureFeature::OrderFlowToxicity), "MarketDepth" => Some(MicrostructureFeature::MarketDepth), _ => None, }) .collect(); let feature_extractor = FeatureExtractor::new(µstructure_features); // Initialize VPIN calculator with optimized configuration for adaptive strategy let vpin_config = VPINConfig { window_size: 50, volume_bucket_size: 10000.0_f64, bucket_volume: 10_000, // 10K volume per bucket bucket_count: 50, // Rolling window of 50 buckets toxicity_threshold: 3_000, // 0.3 toxicity threshold (scaled) max_age_us: 300_000_000, // 5 minutes max age }; let vpin_calculator = VPINCalculator::new(vpin_config); Ok(Self { config, order_book, trade_flow, price_impact, feature_extractor, vpin_calculator, }) } /// Update order book data /// /// # Arguments /// /// * `bids` - New bid levels /// * `asks` - New ask levels /// /// # Returns /// /// Updated order book analysis pub fn update_order_book( &mut self, bids: Vec, asks: Vec, ) -> Result<()> { debug!( "Updating order book with {} bids, {} asks", bids.len(), asks.len() ); self.order_book.update(bids, asks)?; // Update features that depend on order book self.feature_extractor .update_book_features(&self.order_book)?; Ok(()) } /// Process new trade data /// /// # Arguments /// /// * `trade` - New trade to process /// /// # Returns /// /// Updated trade flow analysis pub fn process_trade(&mut self, trade: Trade) -> Result<()> { debug!( "Processing trade: price={}, quantity={}", trade.price, trade.quantity ); // Classify trade if needed let classified_trade = self.trade_flow.classify_trade(trade, &self.order_book)?; // Convert to VPIN MarketDataUpdate format let vpin_update = self.convert_trade_to_market_data_update(&classified_trade)?; // Update VPIN calculator if let Err(e) = self.vpin_calculator.update(&vpin_update) { warn!("VPIN update failed: {:?}", e); } // Update trade flow analysis self.trade_flow.add_trade(classified_trade.clone())?; // Update price impact model self.price_impact .add_trade_observation(&classified_trade, &self.order_book)?; // Update trade-based features self.feature_extractor .update_trade_features(&self.trade_flow)?; Ok(()) } /// Extract current microstructure features /// /// # Returns /// /// Current set of microstructure features pub fn extract_features(&self) -> Result { debug!("Extracting microstructure features"); let features = self.feature_extractor.extract_all_features( &self.order_book, &self.trade_flow, &self.price_impact, &self.vpin_calculator, )?; let market_state = MarketState { spread: self.order_book.get_spread()?, volatility: self.trade_flow.calculate_volatility()?, volume: self.trade_flow.get_recent_volume()?, depth: self.order_book.get_depth()?, }; let quality_indicators = QualityIndicators { book_completeness: self.order_book.calculate_completeness(), trade_completeness: self.trade_flow.calculate_completeness(), data_latency_ms: self.calculate_data_latency(), missing_data_points: self.count_missing_data_points(), }; Ok(MicrostructureFeatures { features, timestamp: chrono::Utc::now(), market_state, quality_indicators, }) } /// Get order book imbalance pub fn get_order_book_imbalance(&self) -> Result { self.order_book.calculate_imbalance() } /// Get current bid-ask spread pub fn get_spread(&self) -> Result { self.order_book.get_spread() } /// Get recent VWAP pub fn get_vwap(&self, window: Duration) -> Result { self.trade_flow.calculate_vwap(window) } /// Estimate price impact for a given trade size pub fn estimate_price_impact(&self, trade_size: f64, side: TradeSide) -> Result { self.price_impact .estimate_impact(trade_size, side, &self.order_book) } /// Calculate data latency fn calculate_data_latency(&self) -> f64 { // Production implementation let now = chrono::Utc::now(); let book_latency = (now - self.order_book.last_update).num_milliseconds() as f64; let trade_latency = if let Some(last_trade) = self.trade_flow.get_last_trade() { (now - last_trade.timestamp).num_milliseconds() as f64 } else { 0.0_f64 }; (book_latency + trade_latency) / 2.0 } /// Count missing data points fn count_missing_data_points(&self) -> u32 { // Production implementation 0 } /// Convert Trade to MarketDataUpdate for VPIN calculator fn convert_trade_to_market_data_update(&self, trade: &Trade) -> Result { // Get current best bid/ask from order book let (bid, ask, bid_size, ask_size) = if let (Some(best_bid), Some(best_ask)) = (self.order_book.bids.front(), self.order_book.asks.front()) { ( (best_bid.price * 10000.0_f64) as i64, // Scale to match VPIN precision (best_ask.price * 10000.0_f64) as i64, best_bid.quantity as u64, best_ask.quantity as u64, ) } else { // Fallback values if order book is empty ( (trade.price * 10000.0_f64) as i64 - 50_i64, // Assume 0.005 spread (trade.price * 10000.0_f64) as i64 + 50_i64, 1000_u64, 1000_u64, ) }; // Convert trade side to VPIN TradeDirection let direction = match trade.side { TradeSide::Buy => Some(TradeDirection::Buy), TradeSide::Sell => Some(TradeDirection::Sell), TradeSide::Unknown => None, }; let side = direction.clone().unwrap_or(TradeDirection::Unknown); Ok(MarketDataUpdate { timestamp: trade.timestamp.timestamp_micros() as u64, symbol: "MULTI".to_owned(), // Generic symbol for adaptive strategy price: (trade.price * 10000.0_f64) as i64, // Scale to match VPIN precision volume: trade.quantity as u64, side, bid, ask, bid_size, ask_size, direction, }) } /// Get current VPIN metrics for order flow toxicity analysis pub fn get_vpin_metrics(&self) -> VPINMetrics { self.vpin_calculator.get_result() } /// Check if current market conditions indicate toxic order flow pub fn is_order_flow_toxic(&self) -> bool { self.vpin_calculator.is_toxic() } /// Generate comprehensive risk signals based on order flow toxicity /// /// Returns a risk signal between -1.0 (very toxic, high risk) and 1.0 (clean flow, low risk) pub fn generate_order_flow_risk_signal(&self) -> f64 { let vpin_metrics = self.vpin_calculator.get_result(); // Base signal from VPIN (inverted because high VPIN = high risk) let vpin_signal = 1.0_f64 - (vpin_metrics.vpin * 2.0_f64).min(1.0_f64); // Scale and cap at 1.0 // Order flow imbalance contribution (extreme imbalances increase risk) let imbalance_penalty = vpin_metrics.order_flow_imbalance.abs() * 0.3_f64; // Bucket fill factor (incomplete buckets may indicate unstable conditions) let stability_factor = if vpin_metrics.bucket_count < 10 { 0.8_f64 // Reduce confidence with few buckets } else { 1.0_f64 }; // Combine factors let risk_signal = (vpin_signal - imbalance_penalty) * stability_factor; // Clamp to [-1, 1] range risk_signal.max(-1.0_f64).min(1.0_f64) } /// Get real-time order flow toxicity alert level /// /// Returns alert severity: 0 = No Alert, 1 = Low, 2 = Medium, 3 = High, 4 = Critical pub fn get_toxicity_alert_level(&self) -> u8 { let vpin_metrics = self.vpin_calculator.get_result(); if vpin_metrics.toxicity_score >= 0.8_f64 { 4_u8 // Critical: Extremely toxic flow } else if vpin_metrics.toxicity_score >= 0.6_f64 { 3_u8 // High: High toxicity } else if vpin_metrics.toxicity_score >= 0.4_f64 { 2_u8 // Medium: Moderate toxicity } else if vpin_metrics.toxicity_score >= 0.2_f64 { 1_u8 // Low: Slight toxicity } else { 0_u8 // No alert: Clean order flow } } /// Generate position sizing recommendation based on order flow toxicity /// /// Returns a multiplier (0.0 to 1.0) to apply to normal position sizes pub fn get_position_sizing_multiplier(&self) -> f64 { let risk_signal = self.generate_order_flow_risk_signal(); let alert_level = self.get_toxicity_alert_level(); match alert_level { 4 => 0.1_f64, // Critical: Reduce positions to 10% 3 => 0.3_f64, // High: Reduce to 30% 2 => 0.6_f64, // Medium: Reduce to 60% 1 => 0.8_f64, // Low: Reduce to 80% _ => (0.5_f64 + risk_signal * 0.5_f64).max(0.2_f64).min(1.0_f64), // Scale with risk signal } } } impl OrderBookTracker { /// Create a new order book tracker pub fn new(max_depth: usize) -> Self { Self { bids: VecDeque::new(), asks: VecDeque::new(), max_depth, last_update: chrono::Utc::now(), imbalance_history: VecDeque::new(), } } /// Update order book levels pub fn update(&mut self, bids: Vec, asks: Vec) -> Result<()> { self.bids = bids.into_iter().take(self.max_depth).collect(); self.asks = asks.into_iter().take(self.max_depth).collect(); self.last_update = chrono::Utc::now(); // Calculate and store imbalance let imbalance = self.calculate_imbalance()?; self.imbalance_history.push_back(imbalance); // Maintain history size if self.imbalance_history.len() > 1000_usize { self.imbalance_history.pop_front(); } Ok(()) } /// Calculate order book imbalance pub fn calculate_imbalance(&self) -> Result { let bid_volume: f64 = self.bids.iter().map(|level| level.quantity).sum(); let ask_volume: f64 = self.asks.iter().map(|level| level.quantity).sum(); if bid_volume + ask_volume == 0.0_f64 { return Ok(0.0_f64); } Ok((bid_volume - ask_volume) / (bid_volume + ask_volume)) } /// Get current bid-ask spread pub fn get_spread(&self) -> Result { if let (Some(best_bid), Some(best_ask)) = (self.bids.front(), self.asks.front()) { Ok(best_ask.price - best_bid.price) } else { anyhow::bail!("Incomplete order book data") } } /// Get order book depth pub fn get_depth(&self) -> Result { let bid_depth: f64 = self.bids.iter().map(|level| level.quantity).sum(); let ask_depth: f64 = self.asks.iter().map(|level| level.quantity).sum(); Ok(bid_depth + ask_depth) } /// Calculate data completeness pub fn calculate_completeness(&self) -> f64 { let expected_levels = self.max_depth * 2; // Both bids and asks let actual_levels = self.bids.len() + self.asks.len(); actual_levels as f64 / expected_levels as f64 } } impl TradeFlowAnalyzer { /// Create a new trade flow analyzer pub fn new(size_buckets: &[f64]) -> Self { Self { recent_trades: VecDeque::new(), size_buckets: size_buckets.to_vec(), vwap_calculator: VWAPCalculator::new(Duration::minutes(5)), trade_classifier: TradeSignClassifier::new(TradeSignMethod::LeeReady), } } /// Add a new trade pub fn add_trade(&mut self, trade: Trade) -> Result<()> { self.vwap_calculator.add_trade(&trade); self.recent_trades.push_back(trade); // Maintain history size if self.recent_trades.len() > 10000_usize { self.recent_trades.pop_front(); } Ok(()) } /// Classify trade direction pub fn classify_trade(&self, mut trade: Trade, order_book: &OrderBookTracker) -> Result { trade.side = self.trade_classifier.classify(&trade, order_book)?; trade.size_category = self.classify_trade_size(trade.quantity); Ok(trade) } /// Classify trade size fn classify_trade_size(&self, quantity: f64) -> TradeSizeCategory { if quantity <= *self.size_buckets.first().unwrap_or(&f64::MAX) { TradeSizeCategory::Small } else if quantity <= *self.size_buckets.get(1).unwrap_or(&f64::MAX) { TradeSizeCategory::Medium } else if quantity <= *self.size_buckets.get(2).unwrap_or(&f64::MAX) { TradeSizeCategory::Large } else { TradeSizeCategory::VeryLarge } } /// Calculate recent volatility pub fn calculate_volatility(&self) -> Result { if self.recent_trades.len() < 2 { return Ok(0.0_f64); } let returns: Vec = self .recent_trades .iter() .collect::>() .windows(2) .filter_map(|window| { let prev = window.first()?; let curr = window.get(1)?; if prev.price == 0.0 { return None; } let price_change = curr.price / prev.price; Some(price_change.ln()) }) .collect(); if returns.is_empty() { return Ok(0.0_f64); } let mean_return = returns.iter().sum::() / returns.len() as f64; let variance = returns .iter() .map(|r| (r - mean_return).powi(2)) .sum::() / returns.len() as f64; Ok(variance.sqrt()) } /// Get recent volume pub fn get_recent_volume(&self) -> Result { let cutoff = chrono::Utc::now() - Duration::minutes(5); let volume = self .recent_trades .iter() .filter(|trade| trade.timestamp > cutoff) .map(|trade| trade.quantity) .sum(); Ok(volume) } /// Calculate VWAP for a time window pub fn calculate_vwap(&self, window: Duration) -> Result { self.vwap_calculator.calculate_vwap(window) } /// Get last trade pub fn get_last_trade(&self) -> Option<&Trade> { self.recent_trades.back() } /// Calculate data completeness pub fn calculate_completeness(&self) -> f64 { // Production - would implement based on expected trade frequency 1.0_f64 } } impl Default for PriceImpactModel { fn default() -> Self { Self::new() } } impl PriceImpactModel { /// Create a new price impact model pub fn new() -> Self { Self { impact_history: VecDeque::new(), linear_coefficient: 0.01_f64, sqrt_coefficient: 0.001_f64, } } /// Add trade observation for impact measurement pub fn add_trade_observation( &mut self, trade: &Trade, order_book: &OrderBookTracker, ) -> Result<()> { // Measure immediate price impact (production implementation) let impact = self.measure_immediate_impact(trade, order_book)?; let measurement = PriceImpactMeasurement { trade_size: trade.quantity, impact, time_elapsed: Duration::zero(), market_state: MarketState { spread: order_book.get_spread().unwrap_or(0.0_f64), volatility: 0.0_f64, // Would calculate from recent data volume: trade.quantity, depth: order_book.get_depth().unwrap_or(0.0_f64), }, }; self.impact_history.push_back(measurement); // Maintain history size if self.impact_history.len() > 1000_usize { self.impact_history.pop_front(); } Ok(()) } /// Estimate price impact for a trade pub fn estimate_impact( &self, trade_size: f64, _side: TradeSide, order_book: &OrderBookTracker, ) -> Result { let depth = order_book.get_depth().unwrap_or(1.0_f64); let spread = order_book.get_spread().unwrap_or(0.01_f64); // Simple impact model: linear + square root components let linear_impact = self.linear_coefficient * trade_size / depth; let sqrt_impact = self.sqrt_coefficient * trade_size.sqrt() / depth.sqrt(); let spread_impact = spread * 0.5_f64; // Half-spread crossing cost Ok(linear_impact + sqrt_impact + spread_impact) } /// Measure immediate price impact (production) fn measure_immediate_impact( &self, _trade: &Trade, _order_book: &OrderBookTracker, ) -> Result { // Production implementation Ok(0.001_f64) } } impl FeatureExtractor { /// Create a new feature extractor pub fn new(features: &[MicrostructureFeature]) -> Self { let enabled_features = features .iter() .map(|f| match f { MicrostructureFeature::BidAskSpread => MicrostructureFeature::BidAskSpread, MicrostructureFeature::OrderBookImbalance => { MicrostructureFeature::OrderBookImbalance }, MicrostructureFeature::TradeSign => MicrostructureFeature::TradeSign, MicrostructureFeature::VolumeProfile => MicrostructureFeature::VolumeProfile, MicrostructureFeature::PriceImpact => MicrostructureFeature::PriceImpact, MicrostructureFeature::MicrostructureNoise => { MicrostructureFeature::MicrostructureNoise }, MicrostructureFeature::OrderFlowToxicity => { MicrostructureFeature::OrderFlowToxicity }, MicrostructureFeature::MarketDepth => MicrostructureFeature::MarketDepth, }) .collect(); Self { enabled_features, feature_history: HashMap::new(), windows: vec![10_usize, 50_usize, 100_usize, 500_usize], // Different calculation windows } } /// Extract all enabled features pub fn extract_all_features( &self, order_book: &OrderBookTracker, trade_flow: &TradeFlowAnalyzer, price_impact: &PriceImpactModel, vpin_calculator: &VPINCalculator, ) -> Result> { let mut features = HashMap::new(); for feature in &self.enabled_features { match feature { MicrostructureFeature::BidAskSpread => { if let Ok(spread) = order_book.get_spread() { features.insert("bid_ask_spread".to_owned(), spread); // Relative spread if let Some(best_bid) = order_book.bids.front() { let relative_spread = spread / best_bid.price; features.insert("relative_spread".to_owned(), relative_spread); } } }, MicrostructureFeature::OrderBookImbalance => { if let Ok(imbalance) = order_book.calculate_imbalance() { features.insert("order_book_imbalance".to_owned(), imbalance); } }, MicrostructureFeature::TradeSign => { let buy_volume = self.calculate_directional_volume(trade_flow, TradeSide::Buy); let sell_volume = self.calculate_directional_volume(trade_flow, TradeSide::Sell); let total_volume = buy_volume + sell_volume; if total_volume > 0.0_f64 { let buy_pressure = buy_volume / total_volume; features.insert("buy_pressure".to_owned(), buy_pressure); features.insert("sell_pressure".to_owned(), 1.0 - buy_pressure); } }, MicrostructureFeature::VolumeProfile => { if let Ok(volume) = trade_flow.get_recent_volume() { features.insert("recent_volume".to_owned(), volume); } }, MicrostructureFeature::PriceImpact => { // Average recent price impact let avg_impact = price_impact .impact_history .iter() .map(|m| m.impact) .sum::() / price_impact.impact_history.len().max(1) as f64; features.insert("average_price_impact".to_owned(), avg_impact); }, MicrostructureFeature::MicrostructureNoise => { if let Ok(volatility) = trade_flow.calculate_volatility() { features.insert("microstructure_noise".to_owned(), volatility); } }, MicrostructureFeature::OrderFlowToxicity => { let vpin_metrics = vpin_calculator.get_result(); features.insert("vpin".to_owned(), vpin_metrics.vpin); features.insert( "order_flow_imbalance".to_owned(), vpin_metrics.order_flow_imbalance, ); features.insert("toxicity_score".to_owned(), vpin_metrics.toxicity_score); features.insert( "is_toxic".to_owned(), if vpin_metrics.is_toxic { 1.0_f64 } else { 0.0_f64 }, ); features.insert( "vpin_bucket_count".to_owned(), vpin_metrics.bucket_count as f64, ); features.insert( "vpin_bucket_fill".to_owned(), vpin_metrics.current_bucket_fill, ); }, _ => { // Production for additional features debug!("Feature {:?} not yet implemented", feature); }, } } Ok(features) } /// Update book-based features pub fn update_book_features(&mut self, _order_book: &OrderBookTracker) -> Result<()> { // Production implementation Ok(()) } /// Update trade-based features pub fn update_trade_features(&mut self, _trade_flow: &TradeFlowAnalyzer) -> Result<()> { // Production implementation Ok(()) } /// Calculate directional volume fn calculate_directional_volume(&self, trade_flow: &TradeFlowAnalyzer, side: TradeSide) -> f64 { let cutoff = chrono::Utc::now() - Duration::minutes(5); trade_flow .recent_trades .iter() .filter(|trade| { trade.timestamp > cutoff && matches!( (&trade.side, &side), (TradeSide::Buy, TradeSide::Buy) | (TradeSide::Sell, TradeSide::Sell) ) }) .map(|trade| trade.quantity) .sum() } } impl VWAPCalculator { /// Create a new VWAP calculator pub fn new(window: Duration) -> Self { Self { price_volume_pairs: VecDeque::new(), window_duration: window, } } /// Add trade to VWAP calculation pub fn add_trade(&mut self, trade: &Trade) { self.price_volume_pairs .push_back((trade.price, trade.quantity, trade.timestamp)); // Remove old data outside window let cutoff = chrono::Utc::now() - self.window_duration; while let Some((_, _, timestamp)) = self.price_volume_pairs.front() { if *timestamp < cutoff { self.price_volume_pairs.pop_front(); } else { break; } } } /// Calculate VWAP for the specified window pub fn calculate_vwap(&self, window: Duration) -> Result { let cutoff = chrono::Utc::now() - window; let (total_pv, total_volume): (f64, f64) = self .price_volume_pairs .iter() .filter(|(_, _, timestamp)| *timestamp > cutoff) .map(|(price, volume, _)| (price * volume, *volume)) .fold((0.0, 0.0), |(acc_pv, acc_vol), (pv, vol)| { (acc_pv + pv, acc_vol + vol) }); if total_volume == 0.0_f64 { anyhow::bail!("No volume data for VWAP calculation"); } Ok(total_pv / total_volume) } } impl TradeSignClassifier { /// Create a new trade sign classifier pub fn new(method: TradeSignMethod) -> Self { Self { method } } /// Classify trade direction pub fn classify(&self, trade: &Trade, order_book: &OrderBookTracker) -> Result { match self.method { TradeSignMethod::QuoteBased => self.classify_quote_based(trade, order_book), TradeSignMethod::TickRule => self.classify_tick_rule(trade), TradeSignMethod::LeeReady => self.classify_lee_ready(trade, order_book), } } /// Quote-based classification fn classify_quote_based( &self, trade: &Trade, order_book: &OrderBookTracker, ) -> Result { if let (Some(best_bid), Some(best_ask)) = (order_book.bids.front(), order_book.asks.front()) { let mid_price = (best_bid.price + best_ask.price) / 2.0_f64; if trade.price > mid_price { Ok(TradeSide::Buy) } else if trade.price < mid_price { Ok(TradeSide::Sell) } else { Ok(TradeSide::Unknown) } } else { Ok(TradeSide::Unknown) } } /// Tick rule classification (production) fn classify_tick_rule(&self, _trade: &Trade) -> Result { // Production implementation Ok(TradeSide::Unknown) } /// Lee-Ready algorithm (production) fn classify_lee_ready( &self, trade: &Trade, order_book: &OrderBookTracker, ) -> Result { // Simplified Lee-Ready: use quote-based as fallback self.classify_quote_based(trade, order_book) } } #[cfg(test)] mod tests { use super::*; use crate::config::MicrostructureConfig; #[test] fn test_microstructure_analyzer_creation() { let mut config = MicrostructureConfig::default(); config.book_depth = 10; config.trade_size_buckets = vec![1000.0_f64, 5000.0_f64, 10000.0_f64]; config.features = vec![]; let analyzer = MicrostructureAnalyzer::new(config); assert!(analyzer.is_ok()); } #[test] fn test_order_book_tracker() { let mut tracker = OrderBookTracker::new(5); let bids = vec![OrderLevel { price: 100.0_f64, quantity: 10.0_f64, order_count: 1, timestamp: chrono::Utc::now(), }]; let asks = vec![OrderLevel { price: 101.0_f64, quantity: 8.0_f64, order_count: 1, timestamp: chrono::Utc::now(), }]; assert!(tracker.update(bids, asks).is_ok()); assert!(tracker.get_spread().unwrap() > 0.0_f64); } #[test] fn test_trade_flow_analyzer() { let mut analyzer = TradeFlowAnalyzer::new(&[1000.0_f64, 5000.0_f64, 10000.0_f64]); let trade = Trade { price: 100.5_f64, quantity: 500.0_f64, timestamp: chrono::Utc::now(), side: TradeSide::Buy, size_category: TradeSizeCategory::Small, }; assert!(analyzer.add_trade(trade).is_ok()); assert!(analyzer.get_recent_volume().unwrap() > 0.0_f64); } #[test] fn test_vwap_calculator() { let mut calc = VWAPCalculator::new(Duration::minutes(5)); let trade1 = Trade { price: 100.0_f64, quantity: 10.0_f64, timestamp: chrono::Utc::now(), side: TradeSide::Buy, size_category: TradeSizeCategory::Small, }; let trade2 = Trade { price: 102.0_f64, quantity: 20.0_f64, timestamp: chrono::Utc::now(), side: TradeSide::Sell, size_category: TradeSizeCategory::Small, }; calc.add_trade(&trade1); calc.add_trade(&trade2); let vwap = calc.calculate_vwap(Duration::minutes(5)).unwrap(); assert!(vwap > 100.0_f64 && vwap < 102.0_f64); } }