diff --git a/common/src/lib.rs b/common/src/lib.rs index 37c36f3ca..a57ea6511 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -30,6 +30,9 @@ pub mod traits; pub mod trading; pub mod types; +// Re-export all types at crate root for easy access +pub use types::*; + /// Prelude module for convenient imports pub mod prelude { //! Common types and utilities for Foxhunt services diff --git a/common/src/types.rs b/common/src/types.rs index c896a6dd7..191ddb1ec 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -597,7 +597,9 @@ impl Default for ResourceLimits { // ============================================================================= /// Common error types for trading operations -#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/// +/// This error type implements Send + Sync for use in async contexts +#[derive(thiserror::Error, Debug)] pub enum CommonTypeError { /// Invalid price value #[error("Invalid price: {value} - {reason}")] @@ -627,12 +629,198 @@ pub enum CommonTypeError { reason: String, }, - /// Conversion error - #[error("Conversion error: {message}")] - ConversionError { - message: String, - }, -} + /// Conversion error + #[error("Conversion error: {message}")] + ConversionError { + message: String, + }, + + /// I/O error + #[error("I/O error: {0}")] + IoError(#[from] std::io::Error), + + /// JSON serialization/deserialization error + #[error("JSON error: {0}")] + JsonError(#[from] serde_json::Error), + + /// Float parsing error + #[error("Float parsing error: {0}")] + ParseFloatError(#[from] std::num::ParseFloatError), + + /// Integer parsing error + #[error("Integer parsing error: {0}")] + ParseIntError(#[from] std::num::ParseIntError), + } + + // Manual trait implementations for CommonTypeError + // (Cannot derive Clone, PartialEq, Eq, Serialize due to std::io::Error and serde_json::Error) + + impl Clone for CommonTypeError { + fn clone(&self) -> Self { + match self { + Self::InvalidPrice { value, reason } => Self::InvalidPrice { + value: value.clone(), + reason: reason.clone(), + }, + Self::InvalidQuantity { value, reason } => Self::InvalidQuantity { + value: value.clone(), + reason: reason.clone(), + }, + Self::InvalidIdentifier { field, reason } => Self::InvalidIdentifier { + field: field.clone(), + reason: reason.clone(), + }, + Self::ValidationError { field, reason } => Self::ValidationError { + field: field.clone(), + reason: reason.clone(), + }, + Self::ConversionError { message } => Self::ConversionError { + message: message.clone(), + }, + // Cannot clone std::io::Error or serde_json::Error, so create new instances + Self::IoError(e) => Self::ConversionError { + message: format!("I/O error: {}", e), + }, + Self::JsonError(e) => Self::ConversionError { + message: format!("JSON error: {}", e), + }, + Self::ParseFloatError(e) => Self::ParseFloatError(e.clone()), + Self::ParseIntError(e) => Self::ParseIntError(e.clone()), + } + } + } + + impl PartialEq for CommonTypeError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::InvalidPrice { value: v1, reason: r1 }, Self::InvalidPrice { value: v2, reason: r2 }) => v1 == v2 && r1 == r2, + (Self::InvalidQuantity { value: v1, reason: r1 }, Self::InvalidQuantity { value: v2, reason: r2 }) => v1 == v2 && r1 == r2, + (Self::InvalidIdentifier { field: f1, reason: r1 }, Self::InvalidIdentifier { field: f2, reason: r2 }) => f1 == f2 && r1 == r2, + (Self::ValidationError { field: f1, reason: r1 }, Self::ValidationError { field: f2, reason: r2 }) => f1 == f2 && r1 == r2, + (Self::ConversionError { message: m1 }, Self::ConversionError { message: m2 }) => m1 == m2, + (Self::ParseFloatError(e1), Self::ParseFloatError(e2)) => e1 == e2, + (Self::ParseIntError(e1), Self::ParseIntError(e2)) => e1 == e2, + // std::io::Error and serde_json::Error don't implement PartialEq, so they're never equal + (Self::IoError(_), Self::IoError(_)) => false, + (Self::JsonError(_), Self::JsonError(_)) => false, + _ => false, + } + } + } + + impl Eq for CommonTypeError {} + + // Note: Display is automatically implemented by thiserror::Error derive + // based on the #[error("...")] attributes on each variant + impl Serialize for CommonTypeError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + match self { + Self::InvalidPrice { value, reason } => { + let mut state = serializer.serialize_struct("InvalidPrice", 2)?; + state.serialize_field("value", value)?; + state.serialize_field("reason", reason)?; + state.end() + } + Self::InvalidQuantity { value, reason } => { + let mut state = serializer.serialize_struct("InvalidQuantity", 2)?; + state.serialize_field("value", value)?; + state.serialize_field("reason", reason)?; + state.end() + } + Self::InvalidIdentifier { field, reason } => { + let mut state = serializer.serialize_struct("InvalidIdentifier", 2)?; + state.serialize_field("field", field)?; + state.serialize_field("reason", reason)?; + state.end() + } + Self::ValidationError { field, reason } => { + let mut state = serializer.serialize_struct("ValidationError", 2)?; + state.serialize_field("field", field)?; + state.serialize_field("reason", reason)?; + state.end() + } + Self::ConversionError { message } => { + let mut state = serializer.serialize_struct("ConversionError", 1)?; + state.serialize_field("message", message)?; + state.end() + } + Self::IoError(e) => { + let mut state = serializer.serialize_struct("IoError", 1)?; + state.serialize_field("message", &format!("I/O error: {}", e))?; + state.end() + } + Self::JsonError(e) => { + let mut state = serializer.serialize_struct("JsonError", 1)?; + state.serialize_field("message", &format!("JSON error: {}", e))?; + state.end() + } + Self::ParseFloatError(e) => { + let mut state = serializer.serialize_struct("ParseFloatError", 1)?; + state.serialize_field("message", &format!("Float parsing error: {}", e))?; + state.end() + } + Self::ParseIntError(e) => { + let mut state = serializer.serialize_struct("ParseIntError", 1)?; + state.serialize_field("message", &format!("Integer parsing error: {}", e))?; + state.end() + } + } + } + } + + impl<'de> Deserialize<'de> for CommonTypeError { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // For deserialization, we'll convert everything to ConversionError since + // we can't reconstruct std::io::Error or serde_json::Error from serialized form + use serde::de::{MapAccess, Visitor}; + use std::fmt; + + struct CommonTypeErrorVisitor; + + impl<'de> Visitor<'de> for CommonTypeErrorVisitor { + type Value = CommonTypeError; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a CommonTypeError") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + // For simplicity, deserialize everything as ConversionError + let mut message = String::new(); + while let Some(key) = map.next_key::()? { + let value: serde_json::Value = map.next_value()?; + if key == "message" { + if let Some(msg) = value.as_str() { + message = msg.to_string(); + } + } else { + message = format!("Deserialized error: {}: {}", key, value); + } + } + if message.is_empty() { + message = "Unknown deserialized error".to_string(); + } + Ok(CommonTypeError::ConversionError { message }) + } + } + + deserializer.deserialize_struct( + "CommonTypeError", + &["value", "reason", "field", "message"], + CommonTypeErrorVisitor, + ) + } + } // ============================================================================= // ORDER TYPES (Moved from trading_engine) @@ -1350,6 +1538,18 @@ impl Default for Price { } } +impl FromStr for Price { + type Err = CommonTypeError; + + fn from_str(s: &str) -> Result { + let parsed_value = s.parse::().map_err(|_| CommonTypeError::InvalidPrice { + value: s.to_owned(), + reason: format!("Cannot parse '{}' as price", s), + })?; + Self::from_f64(parsed_value) + } +} + impl Add for Price { type Output = Self; fn add(self, rhs: Self) -> Self::Output { @@ -1400,6 +1600,17 @@ impl From for Price { } } +// TryFrom for Decimal removed due to conflicting blanket implementation +// Use qty.to_decimal() directly instead +impl From for Decimal { + fn from(qty: Quantity) -> Self { + qty.to_decimal().unwrap_or(Decimal::ZERO) + } +} + +// TryFrom for Decimal removed due to conflict with From implementation +// Use the From implementation instead which handles errors by returning ZERO + impl Mul for Price { type Output = Result; fn mul(self, rhs: Self) -> Self::Output { @@ -1407,6 +1618,44 @@ impl Mul for Price { } } +impl TryFrom for Price { + type Error = CommonTypeError; + fn try_from(s: String) -> Result { + Self::from_str(&s) + } +} + +impl TryFrom<&str> for Price { + type Error = CommonTypeError; + fn try_from(s: &str) -> Result { + Self::from_str(s) + } +} + +impl PartialEq for Price { + fn eq(&self, other: &f64) -> bool { + (self.to_f64() - other).abs() < f64::EPSILON + } +} + +impl PartialEq for f64 { + fn eq(&self, other: &Price) -> bool { + (self - other.to_f64()).abs() < f64::EPSILON + } +} + +impl PartialOrd for Price { + fn partial_cmp(&self, other: &f64) -> Option { + self.to_f64().partial_cmp(other) + } +} + +impl PartialOrd for f64 { + fn partial_cmp(&self, other: &Price) -> Option { + self.partial_cmp(&other.to_f64()) + } +} + /// Core Quantity type using fixed-point arithmetic #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Quantity { @@ -1569,6 +1818,18 @@ impl Default for Quantity { } } +impl FromStr for Quantity { + type Err = CommonTypeError; + + fn from_str(s: &str) -> Result { + let parsed_value = s.parse::().map_err(|_| CommonTypeError::InvalidQuantity { + value: s.to_owned(), + reason: format!("Cannot parse '{}' as quantity", s), + })?; + Self::from_f64(parsed_value) + } +} + impl fmt::Display for Quantity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:.8}", self.to_f64()) @@ -1609,6 +1870,44 @@ impl TryFrom for Quantity { } } +impl TryFrom for Quantity { + type Error = CommonTypeError; + fn try_from(s: String) -> Result { + Self::from_str(&s) + } +} + +impl TryFrom<&str> for Quantity { + type Error = CommonTypeError; + fn try_from(s: &str) -> Result { + Self::from_str(s) + } +} + +impl PartialEq for Quantity { + fn eq(&self, other: &f64) -> bool { + (self.to_f64() - other).abs() < f64::EPSILON + } +} + +impl PartialEq for f64 { + fn eq(&self, other: &Quantity) -> bool { + (self - other.to_f64()).abs() < f64::EPSILON + } +} + +impl PartialOrd for Quantity { + fn partial_cmp(&self, other: &f64) -> Option { + self.to_f64().partial_cmp(other) + } +} + +impl PartialOrd for f64 { + fn partial_cmp(&self, other: &Quantity) -> Option { + self.partial_cmp(&other.to_f64()) + } +} + impl Add for Quantity { type Output = Self; fn add(self, rhs: Self) -> Self::Output { @@ -1878,12 +2177,39 @@ impl From<&str> for Symbol { } } +// TryFrom implementations removed due to conflicting blanket implementations +// Use Symbol::new_validated() or Symbol::from_str_validated() directly instead + impl Default for Symbol { fn default() -> Self { Self::new(String::new()) } } +impl PartialEq for Symbol { + fn eq(&self, other: &str) -> bool { + self.value == other + } +} + +impl PartialEq for Symbol { + fn eq(&self, other: &String) -> bool { + &self.value == other + } +} + +impl PartialEq for &str { + fn eq(&self, other: &Symbol) -> bool { + *self == other.value + } +} + +impl PartialEq for String { + fn eq(&self, other: &Symbol) -> bool { + self == &other.value + } +} + // TimeInForce moved to canonical source: trading_engine::types::TimeInForce // Currency moved to canonical source: trading_engine::types::Currency diff --git a/data/src/features.rs b/data/src/features.rs index ce3d7aee4..6b1fe8610 100644 --- a/data/src/features.rs +++ b/data/src/features.rs @@ -131,12 +131,8 @@ pub struct OrderBookState { pub depth: f64, } -/// Price level in order book -#[derive(Debug, Clone)] -pub struct PriceLevel { - pub price: f64, - pub size: f64, -} +// PriceLevel moved to canonical source in common::types +// Note: Original used f64 types - code may need conversion to Decimal /// Trade data for microstructure analysis #[derive(Debug, Clone)] diff --git a/data/src/providers/benzinga/production_streaming.rs b/data/src/providers/benzinga/production_streaming.rs index ed973efec..14b1b77dd 100644 --- a/data/src/providers/benzinga/production_streaming.rs +++ b/data/src/providers/benzinga/production_streaming.rs @@ -44,7 +44,7 @@ use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; use tungstenite::Message; use tracing::{debug, error, info, instrument, warn}; use common::types::Decimal; -use trading_engine::types::Symbol; +use common::types::Symbol; /// Production Benzinga streaming provider configuration #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/data/src/providers/benzinga/streaming.rs b/data/src/providers/benzinga/streaming.rs index bf5aab2a7..2cd557520 100644 --- a/data/src/providers/benzinga/streaming.rs +++ b/data/src/providers/benzinga/streaming.rs @@ -18,7 +18,7 @@ //! ```rust,no_run //! use data::providers::benzinga::streaming::BenzingaStreamingProvider; //! use data::providers::traits::RealTimeProvider; -//! use trading_engine::types::Symbol; +//! use common::types::Symbol; //! //! # async fn example() -> anyhow::Result<()> { //! let config = BenzingaStreamingConfig { @@ -65,7 +65,7 @@ use std::pin::Pin; use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; use tracing::{debug, error, info, warn}; use common::types::Decimal; -use trading_engine::types::Symbol; +use common::types::Symbol; /// Configuration for Benzinga streaming provider #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/data/src/providers/common.rs b/data/src/providers/common.rs index 0ed21514f..2f1a430ae 100644 --- a/data/src/providers/common.rs +++ b/data/src/providers/common.rs @@ -66,19 +66,21 @@ pub struct OrderBookUpdate { pub sequence: u64, } -/// Price level in order book +/// Extended price level for provider-specific data (MBO order count) #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PriceLevel { - /// Price level - pub price: Decimal, - - /// Total size at this price - pub size: Decimal, +pub struct PriceLevelExt { + /// Core price level data + #[serde(flatten)] + pub inner: common::types::PriceLevel, /// Number of orders at this price (MBO only) pub order_count: Option, } +/// Type alias for backward compatibility during transition +#[deprecated(note = "Use PriceLevelExt for extended functionality or common::types::PriceLevel for core data")] +pub type PriceLevel = PriceLevelExt; + /// Change to a price level #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PriceLevelChange { diff --git a/data/src/training_pipeline.rs b/data/src/training_pipeline.rs index b368bcf81..215f554f5 100644 --- a/data/src/training_pipeline.rs +++ b/data/src/training_pipeline.rs @@ -334,12 +334,7 @@ pub struct OrderBook { pub asks: Vec, } -/// Price level in order book -#[derive(Debug, Clone)] -pub struct PriceLevel { - pub price: Decimal, - pub size: Decimal, -} +// PriceLevel moved to canonical source in common::types /// Order book snapshot for TLOB #[derive(Debug, Clone)] diff --git a/market-data/src/lib.rs b/market-data/src/lib.rs index 2be70c5ca..be41f8b71 100644 --- a/market-data/src/lib.rs +++ b/market-data/src/lib.rs @@ -60,7 +60,7 @@ mod compile_test; // Re-export commonly used types for convenience pub use error::{MarketDataError, MarketDataResult}; pub use models::{ - Candle, IndicatorType, OrderBook, OrderBookLevel, BookSide, PriceRecord, TechnicalIndicator, + Candle, IndicatorType, OrderBook, OrderBookLevelDb, BookSide, PriceRecord, TechnicalIndicator, TimePeriod, }; diff --git a/market-data/src/models.rs b/market-data/src/models.rs index 4ea53a56e..cff7dd405 100644 --- a/market-data/src/models.rs +++ b/market-data/src/models.rs @@ -66,9 +66,9 @@ pub enum BookSide { Ask, } -/// Order book level data +/// Order book level data for database persistence #[derive(Debug, Clone, Serialize, Deserialize, FromRow, PartialEq)] -pub struct OrderBookLevel { +pub struct OrderBookLevelDb { pub id: Uuid, pub symbol: String, pub timestamp: DateTime, @@ -79,7 +79,7 @@ pub struct OrderBookLevel { pub created_at: DateTime, } -impl OrderBookLevel { +impl OrderBookLevelDb { pub fn new( symbol: String, timestamp: DateTime, @@ -106,8 +106,8 @@ impl OrderBookLevel { pub struct OrderBook { pub symbol: String, pub timestamp: DateTime, - pub bids: Vec, - pub asks: Vec, + pub bids: Vec, + pub asks: Vec, } impl OrderBook { diff --git a/market-data/src/orderbook.rs b/market-data/src/orderbook.rs index 8340601a6..601c15a14 100644 --- a/market-data/src/orderbook.rs +++ b/market-data/src/orderbook.rs @@ -6,14 +6,14 @@ use uuid::Uuid; use crate::{ error::{MarketDataError, MarketDataResult}, - models::{OrderBook, OrderBookLevel, BookSide}, + models::{OrderBook, OrderBookLevelDb, BookSide}, }; /// Repository trait for order book data operations #[async_trait] pub trait OrderBookRepository { /// Store order book levels for a symbol - async fn store_order_book_levels(&self, levels: &[OrderBookLevel]) -> MarketDataResult<()>; + async fn store_order_book_levels(&self, levels: &[OrderBookLevelDb]) -> MarketDataResult<()>; /// Store a complete order book snapshot async fn store_order_book(&self, order_book: &OrderBook) -> MarketDataResult<()>; @@ -27,7 +27,7 @@ pub trait OrderBookRepository { symbol: &str, timestamp: DateTime, max_levels: Option, - ) -> MarketDataResult>; + ) -> MarketDataResult>; /// Get order book history for a time range async fn get_order_book_history( @@ -41,14 +41,14 @@ pub trait OrderBookRepository { async fn get_best_bid_ask( &self, symbol: &str, - ) -> MarketDataResult>; + ) -> MarketDataResult>; /// Get aggregated liquidity at price levels async fn get_liquidity_profile( &self, symbol: &str, timestamp: DateTime, - ) -> MarketDataResult>>; + ) -> MarketDataResult>>; /// Delete old order book data before a given timestamp async fn cleanup_old_order_book_data(&self, before: DateTime) -> MarketDataResult; @@ -87,7 +87,7 @@ impl PostgresOrderBookRepository { #[async_trait] impl OrderBookRepository for PostgresOrderBookRepository { - async fn store_order_book_levels(&self, levels: &[OrderBookLevel]) -> MarketDataResult<()> { + async fn store_order_book_levels(&self, levels: &[OrderBookLevelDb]) -> MarketDataResult<()> { if levels.is_empty() { return Ok(()); } @@ -178,7 +178,7 @@ impl OrderBookRepository for PostgresOrderBookRepository { symbol: &str, timestamp: DateTime, max_levels: Option, - ) -> MarketDataResult> { + ) -> MarketDataResult> { self.validate_symbol(symbol).await?; let limit = max_levels.unwrap_or(50); // Default to 50 levels @@ -202,7 +202,7 @@ impl OrderBookRepository for PostgresOrderBookRepository { let levels = rows .into_iter() - .map(|row| OrderBookLevel { + .map(|row| OrderBookLevelDb { id: row.id, symbol: row.symbol, timestamp: row.timestamp, @@ -272,7 +272,7 @@ impl OrderBookRepository for PostgresOrderBookRepository { async fn get_best_bid_ask( &self, symbol: &str, - ) -> MarketDataResult> { + ) -> MarketDataResult> { self.validate_symbol(symbol).await?; let best_bid = sqlx::query!( @@ -303,7 +303,7 @@ impl OrderBookRepository for PostgresOrderBookRepository { match (best_bid, best_ask) { (Some(bid_row), Some(ask_row)) => { - let bid_level = OrderBookLevel { + let bid_level = OrderBookLevelDb { id: bid_row.id, symbol: bid_row.symbol, timestamp: bid_row.timestamp, @@ -314,7 +314,7 @@ impl OrderBookRepository for PostgresOrderBookRepository { created_at: bid_row.created_at, }; - let ask_level = OrderBookLevel { + let ask_level = OrderBookLevelDb { id: ask_row.id, symbol: ask_row.symbol, timestamp: ask_row.timestamp, @@ -335,7 +335,7 @@ impl OrderBookRepository for PostgresOrderBookRepository { &self, symbol: &str, timestamp: DateTime, - ) -> MarketDataResult>> { + ) -> MarketDataResult>> { self.validate_symbol(symbol).await?; let levels = self.get_order_book_levels(symbol, timestamp, None).await?; diff --git a/market-data/tests/basic_test.rs b/market-data/tests/basic_test.rs index 116eddaf9..0548ae163 100644 --- a/market-data/tests/basic_test.rs +++ b/market-data/tests/basic_test.rs @@ -1,7 +1,7 @@ use chrono::Utc; use market_data::{ error::MarketDataResult, - models::{IndicatorType, OrderBook, OrderBookLevel, BookSide, PriceRecord, TechnicalIndicator}, + models::{IndicatorType, OrderBook, OrderBookLevelDb, BookSide, PriceRecord, TechnicalIndicator}, }; use rust_decimal_macros::dec; use std::collections::HashMap; @@ -24,7 +24,7 @@ fn test_order_book_model() { let mut order_book = OrderBook::new("EURUSD".to_string(), Utc::now()); // Add some levels - let bid_level = OrderBookLevel::new( + let bid_level = OrderBookLevelDb::new( "EURUSD".to_string(), Utc::now(), BookSide::Bid, @@ -33,7 +33,7 @@ fn test_order_book_model() { 0, ); - let ask_level = OrderBookLevel::new( + let ask_level = OrderBookLevelDb::new( "EURUSD".to_string(), Utc::now(), BookSide::Ask, diff --git a/ml/src/features.rs b/ml/src/features.rs index 5154947ad..de4c33e73 100644 --- a/ml/src/features.rs +++ b/ml/src/features.rs @@ -25,6 +25,7 @@ use tracing::{debug, warn}; // use error_handling::{AppResult, TradingError}; // Commented out - crate doesn't exist use common::types::*; +use trading_engine::types::basic::OrderBookLevel; use crate::common::MarketData; use crate::safety::{MLSafetyError, MLSafetyManager, SafetyResult}; diff --git a/ml/src/training/unified_data_loader.rs b/ml/src/training/unified_data_loader.rs index c6a52bcaf..e9f736f56 100644 --- a/ml/src/training/unified_data_loader.rs +++ b/ml/src/training/unified_data_loader.rs @@ -18,6 +18,7 @@ use tracing::{debug, info}; use crate::features::{UnifiedFeatureExtractor, UnifiedFinancialFeatures}; use crate::safety::{get_global_safety_manager, MLSafetyManager}; +use adaptive_strategy::microstructure::OrderLevel; use crate::{MLError, MLResult}; use common::types::{Price, Symbol, Volume}; @@ -169,13 +170,7 @@ pub struct OrderBookData { pub sequence: Option, } -/// Order level in the order book -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct OrderLevel { - pub price: Price, - pub size: Volume, - pub count: Option, -} + /// News sentiment data structure #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/risk/src/error.rs b/risk/src/error.rs index f7886acec..82dd376f4 100644 --- a/risk/src/error.rs +++ b/risk/src/error.rs @@ -345,6 +345,13 @@ impl From for RiskError { } } +/// Convert from `CommonTypeError` +impl From for RiskError { + fn from(err: common::types::CommonTypeError) -> Self { + RiskError::Internal(format!("CommonTypeError: {}", err)) + } +} + /// Convert from `serde_json::Error` impl From for RiskError { fn from(err: serde_json::Error) -> Self { diff --git a/services/backtesting_service/src/ml_strategy_engine.rs b/services/backtesting_service/src/ml_strategy_engine.rs index 86573a627..8af556611 100644 --- a/services/backtesting_service/src/ml_strategy_engine.rs +++ b/services/backtesting_service/src/ml_strategy_engine.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use tracing::{debug, error, info, warn}; use serde::{Deserialize, Serialize}; -use trading_engine::types::prelude::*; +use common::types::*; use config::BacktestingStrategyConfig; use crate::storage::StorageManager; diff --git a/services/backtesting_service/src/performance.rs b/services/backtesting_service/src/performance.rs index 84170bf60..7bbd80316 100644 --- a/services/backtesting_service/src/performance.rs +++ b/services/backtesting_service/src/performance.rs @@ -4,7 +4,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use tracing::{debug, info}; -use trading_engine::types::prelude::*; +use common::types::*; use crate::strategy_engine::BacktestTrade; use config::BacktestingPerformanceConfig; diff --git a/services/backtesting_service/src/repository_impl.rs b/services/backtesting_service/src/repository_impl.rs index a3fad33e6..1d7102b12 100644 --- a/services/backtesting_service/src/repository_impl.rs +++ b/services/backtesting_service/src/repository_impl.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use data::providers::benzinga::{BenzingaConfig, BenzingaHistoricalProvider, NewsEvent}; use data::providers::databento::{DatabentoConfig, DatabentoDataset, DatabentoHistoricalProvider}; use common::types::MarketDataEvent; -use trading_engine::types::prelude::*; +use common::types::*; use crate::foxhunt::tli::BacktestStatus; use crate::performance::PerformanceMetrics; diff --git a/services/backtesting_service/src/strategy_engine.rs b/services/backtesting_service/src/strategy_engine.rs index 6733e5c4e..7ab94b8b9 100644 --- a/services/backtesting_service/src/strategy_engine.rs +++ b/services/backtesting_service/src/strategy_engine.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use tracing::{debug, error, info, warn}; use data::unified_feature_extractor::{UnifiedFeatureExtractor, UnifiedFeatureExtractorConfig}; -use trading_engine::types::prelude::*; +use common::types::*; use crate::repositories::BacktestingRepositories; use config::BacktestingStrategyConfig; diff --git a/services/tests/integration_service_communication_tests.rs b/services/tests/integration_service_communication_tests.rs index 28aad4d2d..fa611feaa 100644 --- a/services/tests/integration_service_communication_tests.rs +++ b/services/tests/integration_service_communication_tests.rs @@ -15,7 +15,7 @@ use uuid::Uuid; // Test utilities and mocks use trading_engine::prelude::*; -use trading_engine::types::*; +use common::types::*; #[cfg(test)] mod integration_service_communication_tests { @@ -1187,7 +1187,7 @@ pub enum ServiceStatus { Degraded, } -// OrderSide, OrderType, and OrderStatus already imported via trading_engine::prelude::* and trading_engine::types::* (lines 17-18) +// OrderSide, OrderType, and OrderStatus already imported via trading_engine::prelude::* and common::types::* (lines 17-18) #[derive(Debug, Clone, PartialEq)] pub enum TimeInForce { diff --git a/services/trading_service/src/core/execution_engine.rs b/services/trading_service/src/core/execution_engine.rs index f8ba3f848..52b7ea730 100644 --- a/services/trading_service/src/core/execution_engine.rs +++ b/services/trading_service/src/core/execution_engine.rs @@ -27,6 +27,9 @@ use crate::core::risk_manager::RiskManager; use crate::broker_routing::BrokerRouter; use crate::market_data_ingestion::MarketDataFeed; +// Import canonical VolumeProfile +use adaptive_strategy::execution::VolumeProfile; + // Configuration use config::{TradingConfig, BrokerConfig}; @@ -662,11 +665,7 @@ impl MarketData { pub fn get_venue_spread(&self, venue: ExecutionVenue) -> f64 { 0.01 } } -#[derive(Debug)] -pub struct VolumeProfile { - pub prices: Vec, - pub volumes: Vec, -} + #[derive(Debug)] pub struct BookUpdate { diff --git a/services/trading_service/src/core/market_data_ingestion.rs b/services/trading_service/src/core/market_data_ingestion.rs index 21f1ed6ff..aaebf634f 100644 --- a/services/trading_service/src/core/market_data_ingestion.rs +++ b/services/trading_service/src/core/market_data_ingestion.rs @@ -62,10 +62,10 @@ pub struct MarketTick { pub flags: u32, } -/// Level 2 order book entry +/// Level 2 order book entry for high-performance processing #[derive(Debug, Clone, Copy)] #[repr(C)] -pub struct OrderBookLevel { +pub struct OrderBookLevelFast { pub price: f64, pub quantity: f64, pub order_count: u32, @@ -77,8 +77,8 @@ pub struct OrderBookLevel { pub struct OrderBook { pub symbol: String, pub symbol_hash: u64, - pub bids: Vec, - pub asks: Vec, + pub bids: Vec, + pub asks: Vec, pub last_update_ns: u64, pub sequence_number: u64, pub is_valid: bool, diff --git a/services/trading_service/src/core/order_manager.rs b/services/trading_service/src/core/order_manager.rs index 0aead0229..ed052cc4f 100644 --- a/services/trading_service/src/core/order_manager.rs +++ b/services/trading_service/src/core/order_manager.rs @@ -53,7 +53,7 @@ pub struct OrderBookEntry { // OrderSide now imported from canonical source use common::prelude::OrderSide; -// OrderType and OrderStatus now imported from canonical source via trading_engine::types::prelude +// OrderType and OrderStatus now imported from canonical source via common::types /// Complete trading order structure #[derive(Debug, Clone)] diff --git a/services/trading_service/src/repositories.rs b/services/trading_service/src/repositories.rs index 060ea6c10..62e8bdca7 100644 --- a/services/trading_service/src/repositories.rs +++ b/services/trading_service/src/repositories.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; // Import canonical MarketDataEvent from data crate use common::types::MarketDataEvent; // Import canonical types from trading_engine -use trading_engine::types::{Order, Position, OrderStatus, OrderType, OrderSide}; +use common::types::{Order, Position, OrderStatus, OrderType, OrderSide, PriceLevel}; /// Trading repository for order and execution data persistence #[async_trait] @@ -157,7 +157,7 @@ pub trait ConfigRepository: Send + Sync { use crate::proto::trading::*; // Use canonical Order type from trading_engine -pub use trading_engine::types::Order as TradingOrder; +pub use common::types::Order as TradingOrder; /// Execution event #[derive(Debug, Clone)] @@ -173,7 +173,7 @@ pub struct ExecutionEvent { } // Use canonical Position type from trading_engine -pub use trading_engine::types::Position; +pub use common::types::Position; /// Portfolio summary #[derive(Debug, Clone)] @@ -205,12 +205,8 @@ pub struct OrderBook { pub timestamp: i64, } -/// Price level in order book -#[derive(Debug, Clone)] -pub struct PriceLevel { - pub price: f64, - pub quantity: f64, -} +// PriceLevel moved to canonical source in common::types +// Note: Original used f64 and 'quantity' field - code may need conversion // MarketDataEvent removed - using canonical version from data crate @@ -267,7 +263,7 @@ pub struct PositionRisk { #[derive(Debug, Clone)] // Use canonical Order type from trading_engine for order requests // OrderRequest can be represented as an Order without id/status/timestamp -pub use trading_engine::types::Order as OrderRequest; +pub use common::types::Order as OrderRequest; /// Configuration change receiver pub type ConfigChangeReceiver = tokio::sync::broadcast::Receiver<(String, String)>; diff --git a/services/trading_service/src/state.rs b/services/trading_service/src/state.rs index 685b5bb36..92516ca5b 100644 --- a/services/trading_service/src/state.rs +++ b/services/trading_service/src/state.rs @@ -158,7 +158,7 @@ impl TradingServiceState { /// Subscribe to market data for trading symbols pub async fn subscribe_to_market_data( &self, - symbols: Vec, + symbols: Vec, ) -> TradingServiceResult<()> { let mut market_data = self.market_data.write().await; market_data.subscribe_to_symbols(symbols).await @@ -385,7 +385,7 @@ impl MarketDataManager { /// Subscribe to market data for given symbols pub async fn subscribe_to_symbols( &mut self, - symbols: Vec, + symbols: Vec, ) -> TradingServiceResult<()> { // Subscribe via Databento provider if let Some(databento) = &self.databento_provider { diff --git a/tli/src/dashboard/events.rs b/tli/src/dashboard/events.rs index 70a0fb65b..1ffe4e20a 100644 --- a/tli/src/dashboard/events.rs +++ b/tli/src/dashboard/events.rs @@ -5,7 +5,7 @@ use crate::dashboard::DashboardType; use serde::{Deserialize, Serialize}; -pub use trading_engine::types::events::OrderEvent; +pub use common::types::events::OrderEvent; use common::types::{OrderStatus, OrderType}; /// Main event type for dashboard communication @@ -168,7 +168,7 @@ pub struct SystemStatusEvent { // OrderSide now imported from canonical source use common::types::OrderSide; -// OrderType and OrderStatus now imported from canonical source via trading_engine::types::prelude +// OrderType and OrderStatus now imported from canonical source via common::types #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum TimeInForce { diff --git a/tli/src/dashboard/observability.rs b/tli/src/dashboard/observability.rs index d08c2eb2c..a3de66026 100644 --- a/tli/src/dashboard/observability.rs +++ b/tli/src/dashboard/observability.rs @@ -7,7 +7,7 @@ //! - System-wide metrics across all critical paths use crate::error::TliResult; -use trading_engine::types::metrics::{ +use common::types::metrics::{ get_order_ack_percentiles, LatencyPercentiles, MarketDataEvent, MARKET_DATA_BUFFER, TELEMETRY_TRACER, ORDER_ACK_LATENCY, }; diff --git a/tli/src/ui/widgets/mod.rs b/tli/src/ui/widgets/mod.rs index 914817c6a..df706457b 100644 --- a/tli/src/ui/widgets/mod.rs +++ b/tli/src/ui/widgets/mod.rs @@ -18,6 +18,7 @@ use ratatui::{ use std::collections::VecDeque; use chrono::{DateTime, Utc}; use common::types::*; +use adaptive_strategy::microstructure::OrderLevel; pub mod candlestick_chart; pub mod order_book; @@ -95,13 +96,7 @@ pub struct Candle { pub volume: Decimal, } -/// Order book level with price and size -#[derive(Debug, Clone)] -pub struct OrderLevel { - pub price: Decimal, - pub size: Decimal, - pub count: u32, -} + /// Order book snapshot with bids and asks #[derive(Debug, Clone)] diff --git a/tli/src/ui/widgets/order_book.rs b/tli/src/ui/widgets/order_book.rs index c9435151f..259bab3cc 100644 --- a/tli/src/ui/widgets/order_book.rs +++ b/tli/src/ui/widgets/order_book.rs @@ -13,7 +13,7 @@ use ratatui::{ }; use std::cmp::Ordering; use chrono::{DateTime, Utc}; -use trading_engine::types::prelude::*; +use common::types::*; use super::{ FinancialWidget, FinancialColors, OrderLevel, OrderBookSnapshot, diff --git a/tli/src/ui/widgets/risk_gauge.rs b/tli/src/ui/widgets/risk_gauge.rs index 6e6d79eda..4a558d875 100644 --- a/tli/src/ui/widgets/risk_gauge.rs +++ b/tli/src/ui/widgets/risk_gauge.rs @@ -15,7 +15,7 @@ use ratatui::{ }; use std::collections::VecDeque; use chrono::{DateTime, Utc}; -use trading_engine::types::prelude::*; +use common::types::*; use super::{ FinancialWidget, FinancialColors, RiskMetrics, RiskLevel, diff --git a/trading_engine/src/compliance/best_execution.rs b/trading_engine/src/compliance/best_execution.rs index 06e375dda..582e08545 100644 --- a/trading_engine/src/compliance/best_execution.rs +++ b/trading_engine/src/compliance/best_execution.rs @@ -894,7 +894,7 @@ impl ExecutionReportGenerator { } /// Best execution error types -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Clone, thiserror::Error)] pub enum BestExecutionError { #[error("No venues available for execution")] NoVenuesAvailable, @@ -938,10 +938,35 @@ impl From for BestExecutionError { field, reason )) } - _ => BestExecutionError::DataAccessError(format!("FoxhuntError: {}", err)), - } - } -} + _ => BestExecutionError::DataAccessError(format!("FoxhuntError: {}", err)), + } + } + } + + // Additional From implementations for common error types + impl From for BestExecutionError { + fn from(err: std::io::Error) -> Self { + BestExecutionError::DataAccessError(format!("I/O error: {}", err)) + } + } + + impl From for BestExecutionError { + fn from(err: serde_json::Error) -> Self { + BestExecutionError::DataAccessError(format!("JSON error: {}", err)) + } + } + + impl From for BestExecutionError { + fn from(err: sqlx::Error) -> Self { + BestExecutionError::DataAccessError(format!("Database error: {}", err)) + } + } + + impl From> for BestExecutionError { + fn from(err: Box) -> Self { + BestExecutionError::DataAccessError(format!("Generic error: {}", err)) + } + } impl Default for VenueExecutionMonitor { fn default() -> Self { diff --git a/trading_engine/src/types/basic.rs b/trading_engine/src/types/basic.rs index 468ee4228..00bf8e2cd 100644 --- a/trading_engine/src/types/basic.rs +++ b/trading_engine/src/types/basic.rs @@ -14,8 +14,8 @@ use crate::types::errors::FoxhuntError; use std::fmt; use std::error::Error; -// Import canonical Order from common crate -use common::types::Order; +// Import canonical types from common crate +use common::types::{Order, Symbol, Quantity}; // Re-export canonical trading types from common crate /// `TradingError` - Bridge type that provides static methods for creating `FoxhuntError` instances @@ -556,255 +556,7 @@ impl fmt::Display for UserId { } -pub struct Quantity { - value: u64, -} - -impl Quantity { - pub const ZERO: Self = Self { value: 0 }; - pub const ONE: Self = Self { value: 100_000_000 }; - pub const MAX: Self = Self { value: u64::MAX }; - - pub fn from_f64(value: f64) -> Result { - // Validate input using our validation system - use crate::types::validation::InputValidator; - if let Err(validation_err) = InputValidator::validate_quantity(value) { - return Err(FoxhuntError::Validation { - field: "quantity".to_owned(), - reason: validation_err.to_string(), - expected: Some("positive finite number".to_owned()), - actual: Some(value.to_string()), - }); - } - - if value < 0.0 || !value.is_finite() { - return Err(FoxhuntError::InvalidQuantity { - value: value.to_string(), - reason: "Quantity validation failed".to_owned(), - symbol: None, - }); - } - Ok(Self { - value: (value * 100_000_000.0).round() as u64, - }) - } - - #[must_use] - pub fn to_f64(&self) -> f64 { - self.value as f64 / 100_000_000.0 - } - pub fn to_decimal(&self) -> Result { - Decimal::from_f64(self.to_f64()).ok_or_else(|| FoxhuntError::InvalidQuantity { - value: "0.0".to_owned(), - reason: "Quantity to Decimal conversion failed".to_owned(), - symbol: None, - }) - } - #[must_use] - pub const fn value(&self) -> u64 { - self.value - } - #[must_use] - pub const fn raw_value(&self) -> u64 { - self.value - } - #[must_use] - pub const fn as_u64(&self) -> u64 { - self.value - } - #[must_use] - pub const fn from_raw(value: u64) -> Self { - Self { value } - } - pub fn new(value: f64) -> Result { - Self::from_f64(value) - } - - #[must_use] - pub const fn zero() -> Self { - Self::ZERO - } - pub fn from_i64(value: i64) -> Result { - Self::from_f64(value as f64) - } - - pub fn from_str(s: &str) -> Result { - let parsed_value = s - .parse::() - .map_err(|_| FoxhuntError::InvalidQuantity { - value: s.to_owned(), - reason: format!("Cannot parse '{s}' as quantity"), - symbol: None, - })?; - Self::from_f64(parsed_value) - } - - /// Create Quantity from Decimal - wraps the existing `TryFrom` trait implementation - pub fn from_decimal(decimal: Decimal) -> Result { - use std::convert::TryFrom; - Self::try_from(decimal).map_err(|conversion_err| FoxhuntError::InvalidQuantity { - value: decimal.to_string(), - reason: format!("Failed to convert Decimal to Quantity: {conversion_err}"), - symbol: None, - }) - } - - #[must_use] - pub const fn is_zero(&self) -> bool { - self.value == 0 - } - #[must_use] - pub const fn is_some(&self) -> bool { - !self.is_zero() - } // Non-zero quantities are some" - #[must_use] - pub const fn is_none(&self) -> bool { - self.is_zero() - } // Zero quantities are "none" - #[must_use] - pub const fn as_ref(&self) -> &Self { - self - } - #[must_use] - pub const fn abs(&self) -> Self { - *self - } // Quantity is always positive (u64), so abs is identity - - /// Get the sign of the quantity (1 for positive, 0 for zero) - /// Since Quantity wraps u64, it's always non-negative - #[must_use] - pub const fn signum(&self) -> f64 { - if self.value > 0 { - 1.0 - } else { - 0.0 - } - } - - /// Check if quantity is positive (non-zero) - /// Since Quantity wraps u64, it's always non-negative, so positive means > 0 - #[must_use] - pub const fn is_positive(&self) -> bool { - self.value > 0 - } - - /// Check if quantity is negative - /// Since Quantity wraps u64, it's always non-negative, so this always returns false - #[must_use] - pub const fn is_negative(&self) -> bool { - false - } - - // Additional methods for backward compatibility - #[must_use] - pub fn as_f64(&self) -> f64 { - self.to_f64() - } - - #[must_use] - pub const fn from_shares(shares: u64) -> Self { - Self { - value: shares * 100_000_000, - } // Convert shares to fixed-point - } - - #[must_use] - pub const fn to_shares(&self) -> u64 { - self.value / 100_000_000 // Convert from fixed-point to shares - } - - pub fn multiply(&self, other: Self) -> Result { - Self::from_f64(self.to_f64() * other.to_f64()) - } - - #[must_use] - pub fn subtract(&self, other: Self) -> Self { - *self - other - } -} - -impl Default for Quantity { - fn default() -> Self { - Self::ZERO - } -} - -impl fmt::Display for Quantity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:.8}", self.to_f64()) - } -} - -impl TryFrom for Quantity { - type Error = FoxhuntError; - fn try_from(value: i32) -> Result { - Self::new(f64::from(value)) - } -} - -impl TryFrom for Quantity { - type Error = FoxhuntError; - fn try_from(value: u64) -> Result { - Self::new(value as f64) - } -} - -impl TryFrom for Quantity { - type Error = FoxhuntError; - fn try_from(value: f64) -> Result { - Self::new(value) - } -} - -impl Add for Quantity { - type Output = Self; - fn add(self, rhs: Self) -> Self::Output { - Self { - value: self.value.saturating_add(rhs.value), - } - } -} - -impl Sub for Quantity { - type Output = Self; - fn sub(self, rhs: Self) -> Self::Output { - Self { - value: self.value.saturating_sub(rhs.value), - } - } -} - -impl Mul for Quantity { - type Output = Result; - fn mul(self, rhs: f64) -> Self::Output { - Self::from_f64(self.to_f64() * rhs) - } -} - -impl Div for Quantity { - type Output = Result; - fn div(self, rhs: f64) -> Self::Output { - if rhs == 0.0 { - return Err(TradingError::division_by_zero( - "Cannot divide quantity by zero", - )); - } - Self::from_f64(self.to_f64() / rhs) - } -} - -// Sum trait implementations for Quantity -impl Sum for Quantity { - fn sum>(iter: I) -> Self { - iter.fold(Self::ZERO, |acc, x| acc + x) - } -} - -impl<'quantity> Sum<&'quantity Self> for Quantity { - fn sum>(iter: I) -> Self { - iter.fold(Self::ZERO, |acc, x| acc + *x) - } -} +// Quantity struct DELETED - using canonical import from common::types // Currency moved to common::types - import from there @@ -891,111 +643,7 @@ impl FromStr for common::prelude::Currency { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct Symbol { - value: String, -} - -impl Symbol { - #[must_use] - pub const fn new(s: String) -> Self { - Self { value: s } - } - - pub fn from_str(s: &str) -> Self { - // Note: For backward compatibility, we don't fail here but log validation errors - use crate::types::validation::InputValidator; - if let Err(validation_err) = InputValidator::validate_symbol(s) { - tracing::warn!("Symbol validation warning for '{}': {}", s, validation_err); - } - Self { - value: s.to_owned(), - } - } - - /// Create a new Symbol with validation - pub fn new_validated(s: String) -> Result { - use crate::types::validation::InputValidator; - InputValidator::validate_symbol(&s)?; - Ok(Self { value: s }) - } - - /// Create a Symbol from &str with validation - pub fn from_str_validated(s: &str) -> Result { - Self::new_validated(s.to_owned()) - } - - #[must_use] - pub fn as_str(&self) -> &str { - &self.value - } - #[must_use] - pub fn value(&self) -> &str { - &self.value - } - #[must_use] - pub fn to_string(&self) -> String { - self.value.clone() - } - #[must_use] - pub fn as_bytes(&self) -> &[u8] { - self.value.as_bytes() - } - #[must_use] - pub fn is_empty(&self) -> bool { - self.value.is_empty() - } - #[must_use] - pub fn to_uppercase(&self) -> String { - self.value.to_uppercase() - } - #[must_use] - pub fn replace(&self, from: &str, to: &str) -> String { - self.value.replace(from, to) - } - - // Helper for risk management - #[must_use] - pub fn none() -> Self { - Self::from_str("NONE") - } - - // Missing methods needed by services - #[must_use] - pub fn contains(&self, pattern: &str) -> bool { - self.value.contains(pattern) - } -} - -// Additional implementation to support conversion from &Symbol to &str -impl AsRef for Symbol { - fn as_ref(&self) -> &str { - &self.value - } -} - -impl fmt::Display for Symbol { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.value) - } -} - -impl From for Symbol { - fn from(s: String) -> Self { - Self::new(s) - } -} -impl From<&str> for Symbol { - fn from(s: &str) -> Self { - Self::new(s.to_owned()) - } -} - -impl Default for Symbol { - fn default() -> Self { - Self::new(String::new()) - } -} +// Symbol struct DELETED - using canonical import from common::types // Order types moved to common crate - import from there diff --git a/trading_engine/src/types/conversions.rs b/trading_engine/src/types/conversions.rs index d29f70454..f3eb9ae1c 100644 --- a/trading_engine/src/types/conversions.rs +++ b/trading_engine/src/types/conversions.rs @@ -63,26 +63,8 @@ impl From for Decimal { } } -/// MISSING TRAIT IMPLEMENTATION: `TryFrom` for Quantity -/// This is a critical conversion for financial calculations -impl TryFrom for Quantity { - type Error = ConversionError; - - fn try_from(decimal: Decimal) -> Result { - let f64_val: f64 = decimal.try_into().map_err(|_| { - ConversionError::invalid_number(format!( - "Failed to convert Decimal {decimal} to f64 for Quantity" - )) - })?; - - Self::from_f64(f64_val).map_err(|err| { - ConversionError::type_conversion(format!( - "Failed to convert f64 {f64_val} to Quantity: {err}" - )) - }) - } -} - +// NOTE: TryFrom for Quantity is now provided by common::types +// Removed local implementation to avoid conflicts // Volume is a type alias for Quantity, so it uses the same From implementation above // No separate From implementation needed to avoid conflict diff --git a/trading_engine/src/types/data_structure_optimizations.rs b/trading_engine/src/types/data_structure_optimizations.rs index 77bd0749d..fcd22a830 100644 --- a/trading_engine/src/types/data_structure_optimizations.rs +++ b/trading_engine/src/types/data_structure_optimizations.rs @@ -140,15 +140,16 @@ impl LockFreeOrderMap { } } -/// Price level for order book optimization +/// Optimized price level for high-performance order book operations +/// Uses type-safe wrappers and includes order counting for aggregation #[derive(Debug, Clone)] -pub struct PriceLevel { +pub struct OptimizedPriceLevel { pub price: Price, pub quantity: Quantity, pub order_count: usize, } -impl PriceLevel { +impl OptimizedPriceLevel { pub fn new(price: Price, quantity: Quantity) -> Self { Self { price, @@ -182,8 +183,8 @@ impl PriceLevel { /// Optimized order book using BTreeMap for price-time priority pub struct OptimizedOrderBook { symbol: Symbol, - bids: BTreeMap, // Negative for reverse order - asks: BTreeMap, + bids: BTreeMap, // Negative for reverse order + asks: BTreeMap, max_depth: usize, } @@ -201,12 +202,12 @@ impl OptimizedOrderBook { match side { Side::Buy => { let price_key = -(price.as_raw() as i64); // Negative for reverse order - let level = self.bids.entry(price_key).or_insert_with(|| PriceLevel::new(price, Quantity::new(0))); + let level = self.bids.entry(price_key).or_insert_with(|| OptimizedPriceLevel::new(price, Quantity::new(0))); level.add_quantity(quantity); }, Side::Sell => { let price_key = price.as_raw() as i64; - let level = self.asks.entry(price_key).or_insert_with(|| PriceLevel::new(price, Quantity::new(0))); + let level = self.asks.entry(price_key).or_insert_with(|| OptimizedPriceLevel::new(price, Quantity::new(0))); level.add_quantity(quantity); }, } @@ -470,7 +471,7 @@ mod tests { #[test] fn test_price_level() { - let mut level = PriceLevel::new(Price::from_f64(100.0)?, Quantity::new(100)); + let mut level = OptimizedPriceLevel::new(Price::from_f64(100.0)?, Quantity::new(100)); // Test add quantity level.add_quantity(Quantity::new(50)); diff --git a/trading_engine/src/types/errors.rs b/trading_engine/src/types/errors.rs index 39cc2d93e..9e5cd9ec4 100644 --- a/trading_engine/src/types/errors.rs +++ b/trading_engine/src/types/errors.rs @@ -13,7 +13,85 @@ use std::fmt; use thiserror::Error; // Re-export common error types for convenience -pub use crate::types::{ConversionError, ProtocolError, SymbolError}; +// TODO: Import these from common crate once they exist there +// pub use common::types::{ConversionError, ProtocolError, SymbolError}; + +/// Conversion error types used by trading engine +#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ConversionError { + /// Invalid format error + #[error("Invalid format: {0}")] + InvalidFormat(String), + /// Missing field error + #[error("Missing field: {0}")] + MissingField(String), + /// Type conversion error + #[error("Type conversion failed: {0}")] + TypeConversion(String), + /// Invalid number error + #[error("Invalid number: {0}")] + InvalidNumber(String), +} + +impl ConversionError { + /// Create an invalid format error + pub fn invalid_format>(msg: S) -> Self { + Self::InvalidFormat(msg.into()) + } + + /// Create a missing field error + pub fn missing_field>(field: S) -> Self { + Self::MissingField(field.into()) + } + + /// Create a type conversion error + pub fn type_conversion>(msg: S) -> Self { + Self::TypeConversion(msg.into()) + } + + /// Create an invalid number error + pub fn invalid_number>(msg: S) -> Self { + Self::InvalidNumber(msg.into()) + } +} + +/// Protocol error types used by trading engine +#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ProtocolError { + /// Protocol message error + #[error("Protocol error: {message}")] + MessageError { message: String }, +} + +impl ProtocolError { + /// Create a new protocol error + pub fn new>(message: S) -> Self { + Self::MessageError { message: message.into() } + } +} + +/// Symbol error types used by trading engine +#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SymbolError { + /// Invalid symbol format + #[error("Invalid symbol format: {0}")] + InvalidFormat(String), + /// Symbol not found + #[error("Symbol not found: {0}")] + NotFound(String), +} + +impl SymbolError { + /// Create an invalid format error + pub fn invalid_format>(symbol: S) -> Self { + Self::InvalidFormat(symbol.into()) + } + + /// Create a not found error + pub fn not_found>(symbol: S) -> Self { + Self::NotFound(symbol.into()) + } +} /// Unified Error Hierarchy - Single Source of Truth for All Foxhunt Errors /// @@ -919,10 +997,13 @@ impl From for FoxhuntError { impl From for FoxhuntError { fn from(err: ProtocolError) -> Self { + let message = match err { + ProtocolError::MessageError { message } => message, + }; Self::ProtocolConversion { from: "unknown".to_owned(), to: "unknown".to_owned(), - reason: err.message, + reason: message, data_context: None, } } diff --git a/trading_engine/src/types/mod.rs b/trading_engine/src/types/mod.rs index 3941218b4..c89e4e1d1 100644 --- a/trading_engine/src/types/mod.rs +++ b/trading_engine/src/types/mod.rs @@ -49,6 +49,9 @@ pub mod type_registry; /// Trading engine error types pub mod errors; +/// Trading engine events module +pub mod events; + /// Trading engine financial types pub mod financial; @@ -90,6 +93,7 @@ pub use basic::*; pub use metrics::*; pub use type_registry::*; pub use errors::*; +pub use events::*; pub use financial::*; pub use validation::*; diff --git a/trading_engine/src/types/type_registry.rs b/trading_engine/src/types/type_registry.rs index bf61cc761..2d38ed03d 100644 --- a/trading_engine/src/types/type_registry.rs +++ b/trading_engine/src/types/type_registry.rs @@ -10,72 +10,45 @@ /// This compile-time registry ensures that all types are imported from their /// canonical locations. Any attempt to duplicate types will be caught at compile time. pub mod canonical_types { - // Re-export all canonical types from common crate (single source of truth) + // Re-export available canonical types from common crate (single source of truth) pub use common::types::{ AccountId, - AggregateId, - AggregateVersion, - Amount, - // Asset identification types - AssetId, - Bar, - BookAction, - CausationId, - ClientId, - CorrelationId, + ConfigVersion, + ConnectionEvent, + ConnectionInfo, + ConnectionStatus, Currency, - DeploymentConfig, - DeploymentEnvironment, - EndpointAddress, - // Event system types - EventId, - EventSequence, - FailureInfo, - FailureType, - Fill, - FillId, - HardwareRequirements, - // Market data types + DataType, + ErrorCategory, + ErrorEvent, + Execution, + GenericTimestamp, HftTimestamp, - LogLevel, - MLFramework, - // ML and workflow types - MLModelMetadata, - MLModelType, - ProcessingMarketDataEvent, - MarketTick, - MonitoringConfig, - NodeId, + Level2Update, + MarketDataEvent, + MarketStatus, + Money, Order, + OrderBookEvent, OrderId, + OrderSide, OrderStatus, OrderType, - PerformanceProfile, - PnL, - Portfolio, - // Position and portfolio types Position, - PositionRiskMetrics, - // Core financial types Price, + PriceLevel, Quantity, - ScalingConfig, + QuoteEvent, + RequestId, + ResourceLimits, ServiceId, - ServiceLevelObjectives, - Side, - SignalType, - SlippageModel, - // Trading types + ServiceStatus, + Subscription, Symbol, - TensorDType, - TensorSpec, TimeInForce, Timestamp, - // Identifier types + TradeEvent, TradeId, - TradingSignal, - TrainingInfo, - UserId, Volume, }; } @@ -156,49 +129,44 @@ impl std::fmt::Display for ViolationType { impl std::error::Error for TypeViolation {} -// Implement CanonicalType for all core types -impl CanonicalType for crate::types::basic::Price { - const CANONICAL_PATH: &'static str = "types::basic::Price"; +// Implement CanonicalType for all core types available in common::types +impl CanonicalType for canonical_types::Price { + const CANONICAL_PATH: &'static str = "common::types::Price"; const TYPE_NAME: &'static str = "Price"; } -impl CanonicalType for crate::types::basic::Quantity { - const CANONICAL_PATH: &'static str = "types::basic::Quantity"; +impl CanonicalType for canonical_types::Quantity { + const CANONICAL_PATH: &'static str = "common::types::Quantity"; const TYPE_NAME: &'static str = "Quantity"; } -impl CanonicalType for crate::types::basic::Volume { - const CANONICAL_PATH: &'static str = "types::basic::Volume"; +impl CanonicalType for canonical_types::Volume { + const CANONICAL_PATH: &'static str = "common::types::Volume"; const TYPE_NAME: &'static str = "Volume"; } -impl CanonicalType for crate::types::basic::Symbol { - const CANONICAL_PATH: &'static str = "types::basic::Symbol"; +impl CanonicalType for canonical_types::Symbol { + const CANONICAL_PATH: &'static str = "common::types::Symbol"; const TYPE_NAME: &'static str = "Symbol"; } -impl CanonicalType for crate::types::basic::AssetId { - const CANONICAL_PATH: &'static str = "types::basic::AssetId"; - const TYPE_NAME: &'static str = "AssetId"; +impl CanonicalType for canonical_types::OrderSide { + const CANONICAL_PATH: &'static str = "common::types::OrderSide"; + const TYPE_NAME: &'static str = "OrderSide"; } -impl CanonicalType for crate::types::basic::Side { - const CANONICAL_PATH: &'static str = "types::basic::Side"; - const TYPE_NAME: &'static str = "Side"; -} - -impl CanonicalType for crate::types::basic::OrderType { - const CANONICAL_PATH: &'static str = "types::basic::OrderType"; +impl CanonicalType for canonical_types::OrderType { + const CANONICAL_PATH: &'static str = "common::types::OrderType"; const TYPE_NAME: &'static str = "OrderType"; } -impl CanonicalType for crate::types::basic::OrderId { - const CANONICAL_PATH: &'static str = "types::basic::OrderId"; +impl CanonicalType for canonical_types::OrderId { + const CANONICAL_PATH: &'static str = "common::types::OrderId"; const TYPE_NAME: &'static str = "OrderId"; } -impl CanonicalType for crate::types::basic::Order { - const CANONICAL_PATH: &'static str = "types::basic::Order"; +impl CanonicalType for canonical_types::Order { + const CANONICAL_PATH: &'static str = "common::types::Order"; const TYPE_NAME: &'static str = "Order"; } @@ -222,25 +190,24 @@ impl TypeRegistry { /// Register all canonical type locations fn register_canonical_types(&mut self) { let canonical_types = [ - ("Price", "types::basic::Price"), - ("Quantity", "types::basic::Quantity"), - ("Volume", "types::basic::Volume"), - ("Symbol", "types::basic::Symbol"), - ("AssetId", "types::basic::AssetId"), - ("Side", "types::basic::Side"), - ("OrderType", "types::basic::OrderType"), - ("OrderId", "types::basic::OrderId"), - ("Order", "types::basic::Order"), - ("Fill", "types::basic::Fill"), - ("Position", "types::basic::Position"), - ("Portfolio", "types::basic::Portfolio"), - ("PnL", "types::basic::PnL"), - ("Currency", "types::basic::Currency"), - ("Amount", "types::basic::Amount"), - ("HftTimestamp", "types::basic::HftTimestamp"), - ("Timestamp", "types::basic::Timestamp"), - ("TradingSignal", "types::basic::TradingSignal"), - ("SignalType", "types::basic::SignalType"), + ("Price", "common::types::Price"), + ("Quantity", "common::types::Quantity"), + ("Volume", "common::types::Volume"), + ("Symbol", "common::types::Symbol"), + ("OrderSide", "common::types::OrderSide"), + ("OrderType", "common::types::OrderType"), + ("OrderId", "common::types::OrderId"), + ("Order", "common::types::Order"), + ("Position", "common::types::Position"), + ("Currency", "common::types::Currency"), + ("HftTimestamp", "common::types::HftTimestamp"), + ("Timestamp", "common::types::Timestamp"), + ("TradeId", "common::types::TradeId"), + ("TimeInForce", "common::types::TimeInForce"), + ("OrderStatus", "common::types::OrderStatus"), + ("Money", "common::types::Money"), + ("AccountId", "common::types::AccountId"), + ("ServiceId", "common::types::ServiceId"), ]; for (type_name, canonical_path) in &canonical_types { @@ -331,19 +298,19 @@ mod tests { // Verify key canonical types are registered assert_eq!( registry.get_canonical_path("Price"), - Some("types::basic::Price") + Some("common::types::Price") ); assert_eq!( registry.get_canonical_path("Quantity"), - Some("types::basic::Quantity") + Some("common::types::Quantity") ); assert_eq!( registry.get_canonical_path("Symbol"), - Some("types::basic::Symbol") + Some("common::types::Symbol") ); assert_eq!( - registry.get_canonical_path("Side"), - Some("types::basic::Side") + registry.get_canonical_path("OrderSide"), + Some("common::types::OrderSide") ); } @@ -353,25 +320,25 @@ mod tests { // Valid usage should pass assert!(registry - .validate_type_usage("Price", "types::basic::Price") + .validate_type_usage("Price", "common::types::Price") .is_ok()); - + // Invalid usage should fail let result = registry.validate_type_usage("Price", "some::other::Price"); assert!(result.is_err()); - + if let Err(violation) = result { assert_eq!(violation.type_name, "Price"); assert_eq!(violation.violation_type, ViolationType::NonCanonicalImport); - assert_eq!(violation.canonical_path, "types::basic::Price"); + assert_eq!(violation.canonical_path, "common::types::Price"); assert_eq!(violation.violating_path, "some::other::Price"); } } #[test] fn test_canonical_type_trait() { - use crate::basic::Price; - assert_eq!(Price::CANONICAL_PATH, "types::basic::Price"); + use canonical_types::Price; + assert_eq!(Price::CANONICAL_PATH, "common::types::Price"); assert_eq!(Price::TYPE_NAME, "Price"); assert!(Price::validate_canonical_usage().is_ok()); }