//! Common data types used across services //! //! This module provides shared data types that are used throughout //! the Foxhunt HFT trading system. This includes both infrastructure types //! and core trading types migrated from foxhunt-common-types. use crate::error::ErrorCategory; use chrono::{DateTime, Utc}; // ELIMINATED: Re-exports removed to force explicit imports // NO RE-EXPORTS: Import rust_decimal::Decimal directly in each crate that needs it use rust_decimal::Decimal; // Internal use only - other crates must import directly use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; use std::sync::{Arc, Mutex, RwLock}; use crate::error::{CommonError, ErrorCategory as CommonErrorCategory}; use std::convert::TryFrom; use std::fmt; use std::iter::Sum; use std::num::ParseIntError; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; use std::str::FromStr; use uuid::Uuid; // ============================================================================= // Type Aliases for Complex Types // ============================================================================= /// Common error type for async operations pub type AsyncResult = Result>; /// Thread-safe hash map for shared state pub type SharedHashMap = Arc>>; /// Thread-safe hash map with Mutex for shared state pub type MutexHashMap = Arc>>; /// Thread-safe container for any value pub type SharedValue = Arc>; /// Thread-safe container with Mutex for any value pub type MutexValue = Arc>; // Trading-specific type aliases /// Map of positions by symbol pub type PositionMap = SharedHashMap; /// Map of orders by order ID pub type OrderMap = SharedHashMap; /// Map of accounts by account ID pub type AccountMap = SharedHashMap; /// Map of instruments by instrument ID pub type InstrumentMap = SharedHashMap; /// Map of market data by symbol pub type MarketDataMap = SharedHashMap; /// Cache entry with timestamp pub type CacheEntry = (T, DateTime); /// Cache map with timestamped entries pub type CacheMap = SharedHashMap>; /// Risk factor loadings by instrument pub type RiskFactorMap = SharedHashMap>; /// Performance metrics history pub type PerformanceHistory = SharedHashMap>; /// Model registry for ML models pub type ModelRegistry = SharedHashMap; /// Generic configuration cache pub type ConfigCache = SharedHashMap; // ============================================================================= // Event Types - Moved from trading_engine to enforce pure client architecture // ============================================================================= /// Order events for the complete order lifecycle #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OrderEvent { /// Unique identifier for the order pub order_id: OrderId, /// Trading symbol for the order pub symbol: Symbol, /// Type of order (market, limit, stop, etc.) pub order_type: OrderType, /// Order side (buy or sell) pub side: OrderSide, /// Order quantity pub quantity: Quantity, /// Order price (None for market orders) pub price: Option, /// Timestamp when the event occurred pub timestamp: DateTime, /// Strategy or client identifier pub strategy_id: String, /// Order event type (placed, modified, cancelled) pub event_type: OrderEventType, /// Previous quantity for modifications pub previous_quantity: Option, /// Previous price for modifications pub previous_price: Option, /// Reason for cancellation or modification pub reason: Option, } /// Types of order events #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum OrderEventType { /// Order was placed Placed, /// Order was modified Modified, /// Order was cancelled Cancelled, /// Order was rejected Rejected, } // ============================================================================= // Core Data Types // ============================================================================= /// Unique identifier for services #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ServiceId(pub String); impl ServiceId { /// Create a new service ID pub fn new>(id: S) -> Self { Self(id.into()) } /// Get the inner string value /// /// Get the execution ID as a string slice /// /// Get execution ID as string slice pub fn as_str(&self) -> &str { &self.0 } } impl fmt::Display for ServiceId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl From<&str> for ServiceId { fn from(s: &str) -> Self { Self(s.to_owned()) } } impl From for ServiceId { fn from(s: String) -> Self { Self(s) } } /// Service status enumeration #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ServiceStatus { /// Service is starting up Starting, /// Service is running normally Running, /// Service is degraded but functional Degraded, /// Service is stopping Stopping, /// Service is stopped Stopped, /// Service has encountered an error Error, /// Service is in maintenance mode Maintenance, } impl fmt::Display for ServiceStatus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Starting => write!(f, "STARTING"), Self::Running => write!(f, "RUNNING"), Self::Degraded => write!(f, "DEGRADED"), Self::Stopping => write!(f, "STOPPING"), Self::Stopped => write!(f, "STOPPED"), Self::Error => write!(f, "ERROR"), Self::Maintenance => write!(f, "MAINTENANCE"), } } } impl ServiceStatus { /// Check if the service is healthy pub const fn is_healthy(&self) -> bool { matches!(self, Self::Running | Self::Starting) } /// Check if the service is available for requests pub const fn is_available(&self) -> bool { matches!(self, Self::Running | Self::Degraded) } } /// Configuration version for tracking changes #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ConfigVersion { /// Version number pub version: u64, /// Timestamp when version was created pub timestamp: DateTime, /// Optional description of changes pub description: Option, } impl ConfigVersion { /// Create a new config version pub fn new(version: u64) -> Self { Self { version, timestamp: Utc::now(), description: None, } } /// Create a new config version with description pub fn with_description>(version: u64, description: S) -> Self { Self { version, timestamp: Utc::now(), description: Some(description.into()), } } } // TECHNICAL DEBT ELIMINATED - Use DateTime directly instead of Timestamp alias /// Timestamp type alias for consistency across the system pub type Timestamp = DateTime; /// Request ID for tracing and correlation #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct RequestId(pub Uuid); impl Default for RequestId { /// Create a default request ID with a new UUID fn default() -> Self { Self::new() } } impl RequestId { /// Generate a new random request ID pub fn new() -> Self { Self(Uuid::new_v4()) } /// Create from UUID pub const fn from_uuid(uuid: Uuid) -> Self { Self(uuid) } /// Get the inner UUID pub const fn as_uuid(&self) -> Uuid { self.0 } } // Default implementation is now in the derive macro above // ============================================================================= // MARKET DATA EVENT TYPES (Consolidated from data and trading_engine crates) // ============================================================================= /// Market data event types - CANONICAL DEFINITION #[derive(Debug, Clone, Serialize, Deserialize)] pub enum MarketDataEvent { /// Quote update (bid/ask) Quote(QuoteEvent), /// Trade execution Trade(TradeEvent), /// Aggregate trade data Aggregate(Aggregate), /// Bar/candle data Bar(BarEvent), /// Level 2 market data update Level2(Level2Update), /// Market status update Status(MarketStatus), /// Connection status updates ConnectionStatus(ConnectionEvent), /// Error events with details Error(ErrorEvent), /// Order book update OrderBook(OrderBookEvent), /// Level 2 order book snapshot OrderBookL2Snapshot(OrderBookSnapshot), /// Level 2 order book incremental update OrderBookL2Update(OrderBookUpdate), } /// Quote event structure - CANONICAL DEFINITION #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct QuoteEvent { /// Symbol pub symbol: String, /// Bid price pub bid: Option, /// Ask price pub ask: Option, /// Bid size pub bid_size: Option, /// Ask size pub ask_size: Option, /// Exchange pub exchange: Option, /// Bid exchange pub bid_exchange: Option, /// Ask exchange pub ask_exchange: Option, /// Quote conditions pub conditions: Vec, /// Timestamp pub timestamp: DateTime, /// Sequence number pub sequence: u64, } impl QuoteEvent { /// Create a new quote event #[must_use] pub const fn new(symbol: String, timestamp: DateTime) -> Self { Self { symbol, bid: None, ask: None, bid_size: None, ask_size: None, exchange: None, bid_exchange: None, ask_exchange: None, conditions: Vec::new(), timestamp, sequence: 0, } } /// Set bid price and size pub const fn with_bid(mut self, price: Decimal, size: Decimal) -> Self { self.bid = Some(price); self.bid_size = Some(size); self } /// Set ask price and size pub const fn with_ask(mut self, price: Decimal, size: Decimal) -> Self { self.ask = Some(price); self.ask_size = Some(size); self } /// Set exchange pub fn with_exchange>(mut self, exchange: S) -> Self { self.exchange = Some(exchange.into()); self } /// Set bid exchange pub fn with_bid_exchange>(mut self, exchange: S) -> Self { self.bid_exchange = Some(exchange.into()); self } /// Set ask exchange pub fn with_ask_exchange>(mut self, exchange: S) -> Self { self.ask_exchange = Some(exchange.into()); self } /// Add quote condition pub fn with_condition>(mut self, condition: S) -> Self { self.conditions.push(condition.into()); self } /// Set sequence number pub const fn with_sequence(mut self, sequence: u64) -> Self { self.sequence = sequence; self } /// Get mid price pub fn mid_price(&self) -> Option { match (self.bid, self.ask) { (Some(bid), Some(ask)) => { let sum = bid.checked_add(ask)?; let two = Decimal::from(2); sum.checked_div(two) }, _ => None, } } /// Get spread pub fn spread(&self) -> Option { match (self.bid, self.ask) { (Some(bid), Some(ask)) => ask.checked_sub(bid), _ => None, } } } /// Trade event structure - CANONICAL DEFINITION #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TradeEvent { /// Symbol pub symbol: String, /// Trade price pub price: Decimal, /// Trade size pub size: Decimal, /// Trade ID pub trade_id: Option, /// Exchange pub exchange: Option, /// Trade conditions pub conditions: Vec, /// Timestamp pub timestamp: DateTime, /// Sequence number pub sequence: u64, } impl TradeEvent { /// Create a new trade event #[must_use] pub const fn new( symbol: String, price: Decimal, size: Decimal, timestamp: DateTime, ) -> Self { Self { symbol, price, size, trade_id: None, exchange: None, conditions: Vec::new(), timestamp, sequence: 0, } } /// Set trade ID pub fn with_trade_id>(mut self, trade_id: S) -> Self { self.trade_id = Some(trade_id.into()); self } /// Set exchange pub fn with_exchange>(mut self, exchange: S) -> Self { self.exchange = Some(exchange.into()); self } /// Add trade condition pub fn with_condition>(mut self, condition: S) -> Self { self.conditions.push(condition.into()); self } /// Set sequence number pub const fn with_sequence(mut self, sequence: u64) -> Self { self.sequence = sequence; self } /// Get notional value pub fn notional_value(&self) -> Decimal { self.price.checked_mul(self.size).unwrap_or(Decimal::ZERO) } } /// Aggregate trade data #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Aggregate { /// Symbol pub symbol: String, /// Open price pub open: Decimal, /// High price pub high: Decimal, /// Low price pub low: Decimal, /// Close price pub close: Decimal, /// Volume pub volume: Decimal, /// Volume weighted average price pub vwap: Option, /// Start timestamp pub start_timestamp: DateTime, /// End timestamp pub end_timestamp: DateTime, } /// Bar/candle event structure #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BarEvent { /// Symbol pub symbol: String, /// Open price pub open: Decimal, /// High price pub high: Decimal, /// Low price pub low: Decimal, /// Close price pub close: Decimal, /// Volume pub volume: Decimal, /// Volume weighted average price pub vwap: Option, /// Start timestamp pub start_timestamp: DateTime, /// End timestamp pub end_timestamp: DateTime, /// Timeframe (e.g., "1m", "5m", "1h") pub timeframe: String, } /// Level 2 market data update #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Level2Update { /// Symbol pub symbol: String, /// Bid levels pub bids: Vec, /// Ask levels pub asks: Vec, /// Timestamp pub timestamp: DateTime, } /// Price level for order book #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PriceLevel { /// Price pub price: Decimal, /// Size at this price level pub size: Decimal, } /// Order book snapshot from providers #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OrderBookSnapshot { /// Symbol pub symbol: String, /// Bid levels (price, size) sorted by price descending pub bids: Vec, /// Ask levels (price, size) sorted by price ascending pub asks: Vec, /// Exchange pub exchange: String, /// Timestamp of snapshot pub timestamp: DateTime, /// Sequence number pub sequence: u64, } /// Incremental order book update from providers #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OrderBookUpdate { /// Symbol pub symbol: String, /// Changes to bid levels pub bid_changes: Vec, /// Changes to ask levels pub ask_changes: Vec, /// Exchange pub exchange: String, /// Timestamp of update pub timestamp: DateTime, /// Sequence number pub sequence: u64, } /// Change to a price level #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PriceLevelChange { /// Price level being modified pub price: Decimal, /// New size (0 = remove level) pub size: Decimal, /// Type of change pub change_type: PriceLevelChangeType, /// Side (bid or ask) pub side: OrderBookSide, } /// Type of price level change #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] pub enum PriceLevelChangeType { /// Add new price level Add, /// Update existing price level Update, /// Remove price level Delete, } /// Order book side #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] pub enum OrderBookSide { /// Bid side Bid, /// Ask side Ask, } /// Market status information #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MarketStatus { /// Market pub market: String, /// Status (open, closed, `early_hours`, etc.) pub status: String, /// Timestamp pub timestamp: DateTime, } /// Connection event for status updates #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConnectionEvent { /// Provider name pub provider: String, /// Connection status pub status: ConnectionStatus, /// Optional message pub message: Option, /// Timestamp pub timestamp: DateTime, } /// Connection status enumeration /// /// Connection status for data providers and brokers #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "database", derive(sqlx::Type))] #[cfg_attr( feature = "database", sqlx(type_name = "connection_status", rename_all = "snake_case") )] pub enum ConnectionStatus { /// Successfully connected and operational Connected, /// Disconnected from the service Disconnected, /// Currently attempting to reconnect Reconnecting, } /// Error event structure #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ErrorEvent { /// Provider name pub provider: String, /// Error message pub message: String, /// Error category pub category: ErrorCategory, /// Timestamp pub timestamp: DateTime, } // ErrorCategory is imported from crate::error as CommonErrorCategory /// Order book event #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OrderBookEvent { /// Symbol pub symbol: String, /// Timestamp pub timestamp: DateTime, /// Bid levels pub bids: Vec<(Price, Quantity)>, /// Ask levels pub asks: Vec<(Price, Quantity)>, } /// Data types for subscription #[derive(Debug, Clone, Serialize, Deserialize)] pub enum DataType { /// Real-time quotes Quotes, /// Real-time trades Trades, /// Aggregate/minute bars Aggregates, /// Level 2 order book Level2, /// Market status Status, /// Historical bars/aggregates Bars, /// Order book data OrderBook, /// Volume data Volume, } /// Market data subscription request #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Subscription { /// Symbols to subscribe to pub symbols: Vec, /// Data types to subscribe to pub data_types: Vec, /// Exchange filter (optional) pub exchanges: Vec, } impl MarketDataEvent { /// Get the symbol for any market data event pub fn symbol(&self) -> &str { match self { MarketDataEvent::Quote(q) => &q.symbol, MarketDataEvent::Trade(t) => &t.symbol, MarketDataEvent::Aggregate(a) => &a.symbol, MarketDataEvent::Bar(b) => &b.symbol, MarketDataEvent::Level2(l) => &l.symbol, MarketDataEvent::Status(s) => &s.market, MarketDataEvent::ConnectionStatus(_) => "", MarketDataEvent::Error(_) => "", MarketDataEvent::OrderBook(o) => &o.symbol, MarketDataEvent::OrderBookL2Snapshot(s) => &s.symbol, MarketDataEvent::OrderBookL2Update(u) => &u.symbol, } } /// Get the timestamp for any market data event pub const fn timestamp(&self) -> Option> { match self { MarketDataEvent::Quote(q) => Some(q.timestamp), MarketDataEvent::Trade(t) => Some(t.timestamp), MarketDataEvent::Aggregate(a) => Some(a.end_timestamp), MarketDataEvent::Bar(b) => Some(b.end_timestamp), MarketDataEvent::Level2(l) => Some(l.timestamp), MarketDataEvent::Status(s) => Some(s.timestamp), MarketDataEvent::ConnectionStatus(c) => Some(c.timestamp), MarketDataEvent::Error(e) => Some(e.timestamp), MarketDataEvent::OrderBook(o) => Some(o.timestamp), MarketDataEvent::OrderBookL2Snapshot(s) => Some(s.timestamp), MarketDataEvent::OrderBookL2Update(u) => Some(u.timestamp), } } } impl fmt::Display for RequestId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } /// Connection information for services #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConnectionInfo { /// Host address pub host: String, /// Port number pub port: u16, /// Whether TLS is enabled pub tls: bool, /// Connection timeout in milliseconds pub timeout_ms: u64, } impl ConnectionInfo { /// Create new connection info pub fn new>(host: S, port: u16) -> Self { Self { host: host.into(), port, tls: false, timeout_ms: 5000, } } /// Enable TLS pub const fn with_tls(mut self) -> Self { self.tls = true; self } /// Set timeout pub const fn with_timeout(mut self, timeout_ms: u64) -> Self { self.timeout_ms = timeout_ms; self } /// Get connection URL pub fn url(&self) -> String { let scheme = if self.tls { "https" } else { "http" }; format!("{}://{}:{}", scheme, self.host, self.port) } } /// Resource limits for services #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ResourceLimits { /// Maximum memory usage in bytes pub max_memory_bytes: Option, /// Maximum CPU usage as percentage (0-100) pub max_cpu_percent: Option, /// Maximum number of open file descriptors pub max_file_descriptors: Option, /// Maximum number of network connections pub max_connections: Option, } // ============================================================================= // TRADING TYPES (Migrated from foxhunt-common-types) // ============================================================================= /// Common error types for trading operations /// /// 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}")] InvalidPrice { /// The invalid price value as string value: String, /// Reason why the price is invalid reason: String, }, /// Invalid quantity value #[error("Invalid quantity: {value} - {reason}")] InvalidQuantity { /// The invalid quantity value as string value: String, /// Reason why the quantity is invalid reason: String, }, /// Invalid identifier #[error("Invalid {field}: {reason}")] InvalidIdentifier { /// The field name that contains the invalid identifier field: String, /// Reason why the identifier is invalid reason: String, }, /// Validation error #[error("Validation error for {field}: {reason}")] ValidationError { /// The field name that failed validation field: String, /// Reason why the validation failed reason: String, }, /// Conversion error #[error("Conversion error: {message}")] ConversionError { /// Detailed error message describing the conversion failure 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 { /// Clone the error, converting IO and JSON errors to conversion errors 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 { /// Compare two errors for equality 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_owned(); } } else { message = format!("Deserialized error: {}: {}", key, value); } } if message.is_empty() { message = "Unknown deserialized error".to_owned(); } Ok(CommonTypeError::ConversionError { message }) } } deserializer.deserialize_struct( "CommonTypeError", &["value", "reason", "field", "message"], CommonTypeErrorVisitor, ) } } // ============================================================================= // ORDER TYPES (Moved from trading_engine) // ============================================================================= /// Order type specifying execution behavior - CANONICAL DEFINITION #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[non_exhaustive] pub enum OrderType { /// Market order - executes immediately at current market price Market, /// Limit order - executes only at specified price or better Limit, /// Stop order - becomes market order when stop price is reached Stop, /// Stop-limit order - becomes limit order when stop price is reached StopLimit, /// Iceberg order - large order split into smaller visible portions Iceberg, /// Trailing stop order - stop price adjusts with favorable price movement TrailingStop, /// Hidden order - not displayed in order book Hidden, } impl fmt::Display for OrderType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Market => write!(f, "MARKET"), Self::Limit => write!(f, "LIMIT"), Self::Stop => write!(f, "STOP"), Self::StopLimit => write!(f, "STOP_LIMIT"), Self::Iceberg => write!(f, "ICEBERG"), Self::TrailingStop => write!(f, "TRAILING_STOP"), Self::Hidden => write!(f, "HIDDEN"), } } } impl Default for OrderType { /// Returns the default order type (Market) fn default() -> Self { Self::Market } } impl TryFrom for OrderType { type Error = String; fn try_from(value: i32) -> Result { match value { 0 => Ok(OrderType::Market), 1 => Ok(OrderType::Limit), 2 => Ok(OrderType::Stop), 3 => Ok(OrderType::StopLimit), 4 => Ok(OrderType::Iceberg), 5 => Ok(OrderType::TrailingStop), 6 => Ok(OrderType::Hidden), _ => Err(format!("Invalid OrderType: {}", value)), } } } /// Supported broker types - CANONICAL DEFINITION #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum BrokerType { /// Interactive Brokers TWS/API InteractiveBrokers, /// IC Markets FIX API ICMarkets, /// Paper trading simulation PaperTrading, /// Demo/Test broker Demo, } impl Default for BrokerType { /// Returns the default broker type (`InteractiveBrokers`) fn default() -> Self { Self::InteractiveBrokers } } /// Order status throughout its lifecycle - CANONICAL DEFINITION #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[non_exhaustive] pub enum OrderStatus { /// Order has been created but not yet submitted to broker Created, /// Order has been submitted to broker for execution Submitted, /// Order has been partially executed with remaining quantity PartiallyFilled, /// Order has been completely executed Filled, /// Order was rejected by broker or exchange Rejected, /// Order was cancelled by user or system Cancelled, /// New order accepted by broker New, /// Order expired due to time restrictions Expired, /// Order is pending broker acceptance Pending, /// Order is actively working in the market Working, /// Order status is unknown or not yet determined Unknown, /// Order is temporarily suspended Suspended, /// Order cancellation is pending PendingCancel, /// Order modification is pending PendingReplace, } impl fmt::Display for OrderStatus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Created => write!(f, "CREATED"), Self::Submitted => write!(f, "SUBMITTED"), Self::PartiallyFilled => write!(f, "PARTIALLY_FILLED"), Self::Filled => write!(f, "FILLED"), Self::Rejected => write!(f, "REJECTED"), Self::Cancelled => write!(f, "CANCELLED"), Self::New => write!(f, "NEW"), Self::Expired => write!(f, "EXPIRED"), Self::Pending => write!(f, "PENDING"), Self::Working => write!(f, "WORKING"), Self::Unknown => write!(f, "UNKNOWN"), Self::Suspended => write!(f, "SUSPENDED"), Self::PendingCancel => write!(f, "PENDING_CANCEL"), Self::PendingReplace => write!(f, "PENDING_REPLACE"), } } } impl Default for OrderStatus { /// Returns the default order status (Created) fn default() -> Self { Self::Created } } impl TryFrom for OrderStatus { type Error = String; fn try_from(value: i32) -> Result { match value { 0 => Ok(OrderStatus::Created), 1 => Ok(OrderStatus::Submitted), 2 => Ok(OrderStatus::PartiallyFilled), 3 => Ok(OrderStatus::Filled), 4 => Ok(OrderStatus::Rejected), 5 => Ok(OrderStatus::Cancelled), 6 => Ok(OrderStatus::New), 7 => Ok(OrderStatus::Expired), 8 => Ok(OrderStatus::Pending), 9 => Ok(OrderStatus::Working), 10 => Ok(OrderStatus::Unknown), 11 => Ok(OrderStatus::Suspended), 12 => Ok(OrderStatus::PendingCancel), 13 => Ok(OrderStatus::PendingReplace), _ => Err(format!("Invalid OrderStatus: {}", value)), } } } /// Order side - whether the order is a buy or sell - CANONICAL DEFINITION #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum OrderSide { /// Buy order - purchasing securities Buy, /// Sell order - selling securities Sell, } impl fmt::Display for OrderSide { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Buy => write!(f, "BUY"), Self::Sell => write!(f, "SELL"), } } } impl Default for OrderSide { /// Returns the default order side (Buy) fn default() -> Self { Self::Buy } } impl TryFrom for OrderSide { type Error = String; fn try_from(value: i32) -> Result { match value { 0 => Ok(OrderSide::Buy), 1 => Ok(OrderSide::Sell), _ => Err(format!("Invalid OrderSide: {}", value)), } } } // REMOVED: Side alias - use OrderSide directly /// Currency enumeration - CANONICAL DEFINITION #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "database", derive(sqlx::Type))] pub enum Currency { /// US Dollar USD, /// Euro EUR, /// British Pound Sterling GBP, /// Japanese Yen JPY, /// Swiss Franc CHF, /// Canadian Dollar CAD, /// Australian Dollar AUD, /// New Zealand Dollar NZD, /// Bitcoin BTC, /// Ethereum ETH, } impl fmt::Display for Currency { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::USD => write!(f, "USD"), Self::EUR => write!(f, "EUR"), Self::GBP => write!(f, "GBP"), Self::JPY => write!(f, "JPY"), Self::CHF => write!(f, "CHF"), Self::CAD => write!(f, "CAD"), Self::AUD => write!(f, "AUD"), Self::NZD => write!(f, "NZD"), Self::BTC => write!(f, "BTC"), Self::ETH => write!(f, "ETH"), } } } impl Default for Currency { /// Returns the default currency (USD) fn default() -> Self { Self::USD } } /// Time in force enumeration - CANONICAL DEFINITION #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum TimeInForce { /// Order is valid for the current trading day only Day, /// Order remains active until explicitly cancelled GoodTillCancel, /// Order must be executed immediately or cancelled ImmediateOrCancel, /// Order must be executed completely or cancelled FillOrKill, } impl fmt::Display for TimeInForce { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Day => write!(f, "DAY"), Self::GoodTillCancel => write!(f, "GTC"), Self::ImmediateOrCancel => write!(f, "IOC"), Self::FillOrKill => write!(f, "FOK"), } } } impl Default for TimeInForce { /// Returns the default time in force (Day) fn default() -> Self { Self::Day } } // ============================================================================= // CORE ID TYPES (MIGRATED FROM TRADING_ENGINE) // ============================================================================= // Duplicate TradeId removed - using definition from line 1008 /// Event identifier for tracking system events #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct EventId(String); impl EventId { /// Create a new random event ID pub fn new() -> Self { use uuid::Uuid; Self(Uuid::new_v4().to_string()) } /// Create an event ID from a string, generating new if empty pub fn from_string>(id: S) -> Self { let id = id.into(); if id.is_empty() { Self::new() // Generate new ID if empty } else { Self(id) } } /// Get the string value of the event ID pub fn value(&self) -> &str { &self.0 } } impl fmt::Display for EventId { /// Format the event ID for display fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl From for EventId { /// Create an `EventId` from a String fn from(s: String) -> Self { Self(s) } } impl Default for EventId { /// Create a default `EventId` with a new UUID fn default() -> Self { Self::new() } } // ============================================================================= // Decimal Extension Trait // ============================================================================= /// Extension trait for `rust_decimal::Decimal` with convenience methods pub trait DecimalExt { /// Create Decimal from f64, compatible with legacy `from_f64` usage fn from_f64(value: f64) -> Option where Self: Sized; /// Calculate square root of Decimal fn sqrt(&self) -> Option where Self: Sized; } impl DecimalExt for Decimal { fn from_f64(value: f64) -> Option { Decimal::from_f64_retain(value) } fn sqrt(&self) -> Option { if self.is_sign_negative() { return None; } let value_f64: f64 = self.to_string().parse().ok()?; let sqrt_f64 = value_f64.sqrt(); Decimal::from_f64_retain(sqrt_f64) } } /// Fill identifier with validation #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct FillId(String); impl FillId { /// Create a new fill ID with validation /// /// # Errors /// Returns error if the operation fails pub fn new>(id: S) -> Result { let id = id.into(); if id.is_empty() { return Err(CommonTypeError::ValidationError { field: "fill_id".to_owned(), reason: "Fill ID cannot be empty".to_owned(), }); } Ok(Self(id)) } /// Get the fill ID as a string slice pub fn as_str(&self) -> &str { &self.0 } /// Convert the fill ID into an owned string /// /// Convert the execution ID into an owned string /// /// Convert execution ID into owned string pub fn into_string(self) -> String { self.0 } } impl fmt::Display for FillId { /// Format the fill ID for display fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } /// Aggregate identifier with validation #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct AggregateId(String); impl AggregateId { /// Create a new aggregate ID with validation /// /// # Errors /// Returns error if the operation fails pub fn new>(id: S) -> Result { let id = id.into(); if id.is_empty() { return Err(CommonTypeError::ValidationError { field: "aggregate_id".to_owned(), reason: "Aggregate ID cannot be empty".to_owned(), }); } Ok(Self(id)) } /// Get the aggregate ID as a string slice pub fn as_str(&self) -> &str { &self.0 } /// Convert the aggregate ID into an owned string pub fn into_string(self) -> String { self.0 } } impl fmt::Display for AggregateId { /// Format the aggregate ID for display fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } /// Asset identifier with validation #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct AssetId(String); impl AssetId { /// Create a new asset ID with validation /// /// # Errors /// Returns error if the operation fails pub fn new>(id: S) -> Result { let id = id.into(); if id.is_empty() { return Err(CommonTypeError::ValidationError { field: "asset_id".to_owned(), reason: "Asset ID cannot be empty".to_owned(), }); } Ok(Self(id)) } /// Get the asset ID as a string slice pub fn as_str(&self) -> &str { &self.0 } /// Convert the asset ID into an owned string pub fn into_string(self) -> String { self.0 } } impl fmt::Display for AssetId { /// Format the asset ID for display fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } /// Client identifier with validation #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ClientId(String); impl ClientId { /// Create a new client ID with validation /// /// # Errors /// Returns error if the operation fails pub fn new>(id: S) -> Result { let id = id.into(); if id.is_empty() { return Err(CommonTypeError::ValidationError { field: "client_id".to_owned(), reason: "Client ID cannot be empty".to_owned(), }); } Ok(Self(id)) } /// Get the client ID as a string slice pub fn as_str(&self) -> &str { &self.0 } /// Convert the client ID into an owned string pub fn into_string(self) -> String { self.0 } } impl fmt::Display for ClientId { /// Format the client ID for display fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } // ============================================================================= // CORE TRADING TYPES - MIGRATED FROM TRADING_ENGINE // ============================================================================= /// Canonical Order struct - UNIFIED DEFINITION based on Agent 1's comprehensive analysis /// /// This represents the single source of truth for Order across all services #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "database", derive(sqlx::FromRow))] pub struct Order { // Core Identity /// Unique order identifier pub id: OrderId, /// Client-provided order identifier pub client_order_id: Option, /// Broker-assigned order identifier pub broker_order_id: Option, /// Account identifier for the order pub account_id: Option, // Trading Details /// Trading symbol for the order pub symbol: Symbol, /// Order side (buy or sell) pub side: OrderSide, /// Type of order (market, limit, etc.) pub order_type: OrderType, /// Current status of the order pub status: OrderStatus, /// Time in force policy pub time_in_force: TimeInForce, // Quantities & Pricing /// Total order quantity pub quantity: Quantity, /// Limit price for the order pub price: Option, /// Stop price for stop orders pub stop_price: Option, /// Quantity that has been filled pub filled_quantity: Quantity, /// Remaining quantity to be filled pub remaining_quantity: Quantity, /// Average execution price pub average_price: Option, /// Alias for `average_price` for database compatibility pub avg_fill_price: Option, /// Alias for `average_price` for API compatibility pub average_fill_price: Option, /// Exchange-assigned order identifier pub exchange_order_id: Option, // Strategy Fields (from Agent 1) /// Parent order ID for iceberg/algo orders pub parent_id: Option, /// Execution algorithm name pub execution_algorithm: Option, /// Execution algorithm parameters stored as JSON pub execution_params: Value, // Risk Management (from Agent 1) /// Stop loss price for risk management pub stop_loss: Option, /// Take profit price for profit taking pub take_profit: Option, // Timestamps /// Order creation timestamp pub created_at: HftTimestamp, /// Last update timestamp pub updated_at: Option, /// Order expiration timestamp pub expires_at: Option, // Extensibility /// Additional order metadata stored as JSON pub metadata: Value, } impl Order { /// Create a new order with canonical fields pub fn new( symbol: Symbol, side: OrderSide, quantity: Quantity, price: Option, order_type: OrderType, ) -> Self { let now = HftTimestamp::now_or_zero(); Self { // Core Identity id: OrderId::new(), client_order_id: None, broker_order_id: None, account_id: None, // Trading Details symbol, side, order_type, status: OrderStatus::Created, time_in_force: TimeInForce::default(), // Quantities & Pricing quantity, price, stop_price: None, filled_quantity: Quantity::ZERO, remaining_quantity: quantity, average_price: None, avg_fill_price: None, // Database compatibility alias average_fill_price: None, // API compatibility alias exchange_order_id: None, // Strategy Fields parent_id: None, execution_algorithm: None, execution_params: serde_json::json!({}), // Risk Management stop_loss: None, take_profit: None, // Timestamps created_at: now, updated_at: None, expires_at: None, // Extensibility metadata: serde_json::json!({}), } } /// Check if the order is fully filled pub fn is_filled(&self) -> bool { self.filled_quantity == self.quantity } /// Check if the order is partially filled pub fn is_partially_filled(&self) -> bool { self.filled_quantity > Quantity::ZERO && self.filled_quantity < self.quantity } /// Calculate fill percentage #[allow(clippy::float_arithmetic)] pub fn fill_percentage(&self) -> f64 { if self.quantity.is_zero() { 0.0 } else { (self.filled_quantity.to_f64() / self.quantity.to_f64()) * 100.0 } } /// Set client order ID for tracking pub fn with_client_order_id(mut self, client_order_id: String) -> Self { self.client_order_id = Some(client_order_id); self } /// Set account ID pub fn with_account_id(mut self, account_id: String) -> Self { self.account_id = Some(account_id); self } /// Set time in force pub const fn with_time_in_force(mut self, time_in_force: TimeInForce) -> Self { self.time_in_force = time_in_force; self } /// Set stop price pub const fn with_stop_price(mut self, stop_price: Price) -> Self { self.stop_price = Some(stop_price); self } /// Set execution algorithm pub fn with_execution_algorithm(mut self, algorithm: String) -> Self { self.execution_algorithm = Some(algorithm); self } /// Add execution parameter pub fn with_execution_param(mut self, key: String, value: f64) -> Self { if let Some(obj) = self.execution_params.as_object_mut() { obj.insert(key, serde_json::to_value(value).unwrap_or(Value::Null)); } else { let mut map = serde_json::Map::new(); map.insert(key, serde_json::to_value(value).unwrap_or(Value::Null)); self.execution_params = Value::Object(map); } self } /// Set stop loss pub const fn with_stop_loss(mut self, stop_loss: Price) -> Self { self.stop_loss = Some(stop_loss); self } /// Set take profit pub const fn with_take_profit(mut self, take_profit: Price) -> Self { self.take_profit = Some(take_profit); self } /// Add metadata pub fn with_metadata(mut self, key: String, value: String) -> Self { if let Some(obj) = self.metadata.as_object_mut() { obj.insert(key, Value::String(value)); } else { let mut map = serde_json::Map::new(); map.insert(key, Value::String(value)); self.metadata = Value::Object(map); } self } /// Update order status and timestamp pub fn update_status(&mut self, status: OrderStatus) { self.status = status; self.updated_at = Some(HftTimestamp::now_or_zero()); } /// Fill order with given quantity and price /// /// # Errors /// Returns error if the operation fails #[allow(clippy::float_arithmetic)] pub fn fill( &mut self, fill_quantity: Quantity, fill_price: Price, ) -> Result<(), CommonTypeError> { // Use Add trait instead of checked_add (Quantity doesn't have checked_add method) let new_filled_value = self.filled_quantity.to_f64() + fill_quantity.to_f64(); if !new_filled_value.is_finite() || new_filled_value < 0.0 { return Err(CommonTypeError::ValidationError { field: "fill_quantity".to_owned(), reason: "Fill quantity overflow".to_owned(), }); } let new_filled = Quantity::from_f64(new_filled_value).map_err(|e| CommonTypeError::ValidationError { field: "fill_quantity".to_owned(), reason: format!("Fill quantity overflow: {}", e), })?; if new_filled > self.quantity { return Err(CommonTypeError::ValidationError { field: "fill_quantity".to_owned(), reason: "Fill quantity exceeds remaining quantity".to_owned(), }); } // Update filled quantity let previous_filled = self.filled_quantity; let new_filled_value = self .filled_quantity .value .checked_add(fill_quantity.value) .ok_or_else(|| CommonTypeError::ValidationError { field: "filled_quantity".to_owned(), reason: "Filled quantity overflow".to_owned(), })?; self.filled_quantity = Quantity { value: new_filled_value, }; let new_remaining_value = self .quantity .value .checked_sub(self.filled_quantity.value) .ok_or_else(|| CommonTypeError::ValidationError { field: "remaining_quantity".to_owned(), reason: "Remaining quantity underflow".to_owned(), })?; self.remaining_quantity = Quantity { value: new_remaining_value, }; // Update average price if let Some(avg_price) = self.average_price { let total_value = avg_price.to_f64() * previous_filled.to_f64() + fill_price.to_f64() * fill_quantity.to_f64(); let new_avg = Some( Price::from_f64(total_value / self.filled_quantity.to_f64()).unwrap_or(fill_price), ); self.average_price = new_avg; self.avg_fill_price = new_avg; // Keep in sync } else { self.average_price = Some(fill_price); self.avg_fill_price = Some(fill_price); // Keep in sync } // Update status if self.is_filled() { self.update_status(OrderStatus::Filled); } else { self.update_status(OrderStatus::PartiallyFilled); } Ok(()) } /// Create a limit order - convenience constructor pub fn limit(symbol: Symbol, side: OrderSide, quantity: Quantity, price: Price) -> Self { Self::new(symbol, side, quantity, Some(price), OrderType::Limit) } /// Create a market order - convenience constructor pub fn market(symbol: Symbol, side: OrderSide, quantity: Quantity) -> Self { Self::new(symbol, side, quantity, None, OrderType::Market) } /// Get symbol hash for performance-critical operations pub fn symbol_hash(&self) -> i64 { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new(); self.symbol.as_str().hash(&mut hasher); i64::try_from(hasher.finish()).unwrap_or(0) } /// Get order timestamp pub const fn timestamp(&self) -> HftTimestamp { self.created_at } } impl Default for Order { fn default() -> Self { Self { id: OrderId::new(), client_order_id: None, broker_order_id: None, account_id: None, symbol: Symbol::from("DEFAULT"), side: OrderSide::Buy, order_type: OrderType::Market, status: OrderStatus::Created, time_in_force: TimeInForce::Day, quantity: Quantity::ONE, price: None, stop_price: None, filled_quantity: Quantity::ZERO, remaining_quantity: Quantity::ONE, average_price: None, avg_fill_price: None, average_fill_price: None, exchange_order_id: None, parent_id: None, execution_algorithm: None, execution_params: serde_json::json!({}), stop_loss: None, take_profit: None, created_at: HftTimestamp::now().unwrap_or(HftTimestamp { nanos: 0 }), updated_at: None, expires_at: None, metadata: serde_json::json!({}), } } } /// Represents a trading position - CANONICAL DEFINITION #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "database", derive(sqlx::FromRow))] pub struct Position { /// Unique position identifier pub id: Uuid, /// Trading symbol pub symbol: String, /// Position quantity (positive for long, negative for short) pub quantity: Decimal, /// Average entry price pub avg_price: Decimal, /// Average cost per share pub avg_cost: Decimal, /// Cost basis for tax calculations pub basis: Decimal, /// Average entry price pub average_price: Decimal, /// Market value of position pub market_value: Decimal, /// Unrealized P&L pub unrealized_pnl: Decimal, /// Realized P&L pub realized_pnl: Decimal, /// Position creation timestamp pub created_at: DateTime, /// Last update timestamp pub updated_at: DateTime, /// Last updated timestamp pub last_updated: DateTime, /// Current market price (for P&L calculation) pub current_price: Option, /// Position size in base currency pub notional_value: Decimal, /// Margin requirement pub margin_requirement: Decimal, } impl Position { /// Create a new position pub fn new(symbol: String, quantity: Decimal, avg_price: Decimal) -> Self { let now = Utc::now(); let notional_value = quantity .abs() .checked_mul(avg_price) .unwrap_or(Decimal::ZERO); Self { id: Uuid::new_v4(), symbol, quantity, avg_price, avg_cost: avg_price, // Keep avg_cost synchronized with avg_price basis: quantity.checked_mul(avg_price).unwrap_or(Decimal::ZERO), // Cost basis calculation average_price: avg_price, // Same as avg_price for compatibility market_value: notional_value, // Initialize market value to notional value unrealized_pnl: Decimal::ZERO, realized_pnl: Decimal::ZERO, created_at: now, updated_at: now, last_updated: now, // Same as updated_at for compatibility current_price: None, notional_value, margin_requirement: notional_value .checked_mul(Decimal::from_str_exact("0.02").unwrap_or(Decimal::ZERO)) .unwrap_or(Decimal::ZERO), // 2% margin } } /// Check if position is long pub fn is_long(&self) -> bool { self.quantity > Decimal::ZERO } /// Check if position is short pub fn is_short(&self) -> bool { self.quantity < Decimal::ZERO } /// Calculate unrealized P&L based on current price pub fn calculate_unrealized_pnl(&mut self, current_price: Decimal) { self.current_price = Some(current_price); self.market_value = self .quantity .abs() .checked_mul(current_price) .unwrap_or(Decimal::ZERO); // For both long and short: quantity * (current_price - avg_price) let price_diff = current_price .checked_sub(self.avg_price) .unwrap_or(Decimal::ZERO); self.unrealized_pnl = self .quantity .checked_mul(price_diff) .unwrap_or(Decimal::ZERO); let now = Utc::now(); self.updated_at = now; self.last_updated = now; // Keep alias synchronized } /// Get total P&L (realized + unrealized) pub fn total_pnl(&self) -> Decimal { self.realized_pnl .checked_add(self.unrealized_pnl) .unwrap_or(Decimal::ZERO) } /// Calculate return on investment percentage pub fn roi_percentage(&self) -> Decimal { if self.notional_value.is_zero() { Decimal::ZERO } else { let pnl_ratio = self .total_pnl() .checked_div(self.notional_value) .unwrap_or(Decimal::ZERO); pnl_ratio .checked_mul(Decimal::from(100)) .unwrap_or(Decimal::ZERO) } } } /// Represents a trade execution - CANONICAL DEFINITION #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "database", derive(sqlx::FromRow))] pub struct Execution { /// Unique execution identifier pub id: Uuid, /// Related order ID pub order_id: Uuid, /// Trading symbol pub symbol: String, /// Executed quantity pub quantity: Decimal, /// Execution price pub price: Decimal, /// Execution side pub side: OrderSide, /// Trading fees pub fees: Decimal, /// Fee currency pub fee_currency: String, /// Execution timestamp pub executed_at: DateTime, /// Execution timestamp pub timestamp: DateTime, /// Symbol hash for performance pub symbol_hash: i64, /// Broker execution ID pub broker_execution_id: Option, /// Counterparty information pub counterparty: Option, /// Trade venue pub venue: Option, /// Gross trade value pub gross_value: Decimal, /// Net trade value (after fees) pub net_value: Decimal, } impl Execution { /// Create a new execution pub fn new( order_id: Uuid, symbol: String, quantity: Decimal, price: Decimal, side: OrderSide, fees: Decimal, ) -> Self { let gross_value = quantity.checked_mul(price).unwrap_or(Decimal::ZERO); let net_value = if side == OrderSide::Buy { gross_value.checked_add(fees).unwrap_or(gross_value) } else { gross_value.checked_sub(fees).unwrap_or(gross_value) }; let now = Utc::now(); let symbol_hash = Self::hash_symbol(&symbol); Self { id: Uuid::new_v4(), order_id, symbol, quantity, price, side, fees, fee_currency: "USD".to_owned(), // Default to USD executed_at: now, timestamp: now, // Same as executed_at for compatibility symbol_hash, broker_execution_id: None, counterparty: None, venue: None, gross_value, net_value, } } /// Calculate effective price including fees pub fn effective_price(&self) -> Decimal { if self.quantity.is_zero() { self.price } else { self.net_value .checked_div(self.quantity) .unwrap_or(self.price) } } /// Hash symbol for performance fn hash_symbol(symbol: &str) -> i64 { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new(); symbol.hash(&mut hasher); i64::try_from(hasher.finish()).unwrap_or(0) } } /// Core Price type using fixed-point arithmetic for precision #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Price { value: u64, } impl Price { /// Zero price constant pub const ZERO: Self = Self { value: 0 }; /// One unit price constant (1.0) pub const ONE: Self = Self { value: 100_000_000 }; /// One cent constant (0.01) pub const CENT: Self = Self { value: 1_000_000 }; /// Maximum price value pub const MAX: Self = Self { value: u64::MAX }; /// Create a Price from a floating-point value /// /// # Errors /// Returns error if the operation fails #[allow(clippy::as_conversions)] #[allow(clippy::float_arithmetic)] pub fn from_f64(value: f64) -> Result { if value < 0.0_f64 || !value.is_finite() { return Err(CommonTypeError::InvalidPrice { value: value.to_string(), reason: "Price validation failed".to_owned(), }); } Ok(Self { #[allow(clippy::as_conversions)] #[allow(clippy::float_arithmetic)] value: (value * 100_000_000.0).round() as u64, }) } /// Convert to floating-point representation #[must_use] #[allow(clippy::float_arithmetic)] /// Convert the quantity to a floating point value #[allow(clippy::as_conversions)] pub fn to_f64(&self) -> f64 { #[allow(clippy::as_conversions)] { self.value as f64 / 100_000_000.0 } } /// Get floating-point representation (alias for `to_f64`) /// /// Convert quantity to f64 representation /// /// Convert quantity to f64 representation /// /// Convert quantity to f64 representation #[must_use] pub fn as_f64(&self) -> f64 { self.to_f64() } /// Create a zero price /// /// Create a zero quantity /// /// Create zero quantity #[must_use] pub const fn zero() -> Self { Self::ZERO } /// Convert to Decimal type for precise calculations /// /// # Errors /// Returns error if the operation fails pub fn to_decimal(&self) -> Result { ::from_f64(self.to_f64()).ok_or_else(|| { CommonTypeError::InvalidPrice { value: "0.0".to_owned(), reason: "Price to Decimal conversion failed".to_owned(), } }) } /// Create a Price from a Decimal value #[must_use] pub fn from_decimal(decimal: Decimal) -> Self { Self::from(decimal) } /// Create a new Price (alias for `from_f64`) /// /// Create a new quantity from a floating point value /// /// Create new quantity from f64 value /// /// # Errors /// Returns error if the operation fails pub fn new(value: f64) -> Result { Self::from_f64(value) } /// Get the raw internal value representation /// /// Get the raw internal value /// /// Get the raw internal value representation #[must_use] pub const fn raw_value(&self) -> u64 { self.value } /// Get the price as a u64 value (same as `raw_value`) /// /// Convert to u64 representation /// /// Convert quantity to u64 representation #[must_use] pub const fn as_u64(&self) -> u64 { self.value } /// Create a Price from a raw u64 value /// /// Create a quantity from raw internal value /// /// Create quantity from raw u64 value #[must_use] pub const fn from_raw(value: u64) -> Self { Self { value } } /// Convert price to cents (divides by 1M for 8 decimal places) #[must_use] #[allow(clippy::integer_division)] pub const fn to_cents(&self) -> u64 { self.value / 1_000_000 } /// Create a Price from cents value #[must_use] pub const fn from_cents(cents: u64) -> Self { Self { value: cents.saturating_mul(1_000_000), } } /// Check if the price is zero /// /// Check if the quantity is zero /// /// Check if quantity is zero #[must_use] pub const fn is_zero(&self) -> bool { self.value == 0 } /// Check if the price is non-zero (has some value) /// /// Check if the quantity is non-zero (has some value) /// /// Check if quantity has a non-zero value #[must_use] pub const fn is_some(&self) -> bool { !self.is_zero() } /// Check if the price is zero (has no value) /// /// Check if the quantity is zero (has no value) /// /// Check if quantity is zero (none) #[must_use] pub const fn is_none(&self) -> bool { self.is_zero() } /// Get a reference to this price /// /// Get a reference to self /// /// Get a reference to self #[must_use] pub const fn as_ref(&self) -> &Self { self } /// Get the absolute value of the price (prices are always positive) /// /// Get the absolute value (quantities are always positive) /// /// Get absolute value (always positive for Quantity) #[must_use] pub const fn abs(&self) -> Self { *self } /// Multiply this price by another price /// /// # Errors /// Returns error if the operation fails #[allow(clippy::arithmetic_side_effects)] pub fn multiply(&self, other: Self) -> Result { #[allow(clippy::arithmetic_side_effects)] let result = *self * other; result } /// Subtract another price from this price /// /// Subtract another quantity from this quantity /// /// Subtract another quantity from this quantity /// /// Subtract another quantity from this quantity /// /// Subtract another quantity from this quantity #[must_use] #[allow(clippy::arithmetic_side_effects)] pub fn subtract(&self, other: Self) -> Self { *self - other } /// Divide this price by a floating point divisor /// /// # Errors /// Returns error if the operation fails pub fn divide(&self, divisor: f64) -> Result { if divisor == 0.0 { return Err(CommonTypeError::InvalidPrice { value: format!("{:?}", self), reason: "Division by zero".to_owned(), }); } #[allow(clippy::cast_precision_loss)] #[allow(clippy::arithmetic_side_effects)] let result = (self.value as f64 / divisor) as u64; Ok(Self { value: result }) } } impl fmt::Display for Price { /// Format the price for display with 8 decimal places fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:.8}", self.to_f64()) } } impl Default for Price { /// Returns the default price (zero) fn default() -> Self { Self::ZERO } } impl FromStr for Price { type Err = CommonTypeError; fn from_str(s: &str) -> Result { let parsed_value = s .parse::() .map_err(|e| CommonTypeError::InvalidPrice { value: s.to_owned(), reason: format!("Cannot parse '{}' as price: {}", s, e), })?; Self::from_f64(parsed_value) } } impl Add for Price { type Output = Self; fn add(self, rhs: Self) -> Self::Output { Self { value: self.value.saturating_add(rhs.value), } } } impl Sub for Price { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { Self { value: self.value.saturating_sub(rhs.value), } } } impl Mul for Price { #[allow(clippy::float_arithmetic)] type Output = Result; fn mul(self, rhs: f64) -> Self::Output { Self::from_f64(self.to_f64() * rhs) } } #[allow(clippy::float_arithmetic)] impl Div for Price { type Output = Result; fn div(self, rhs: f64) -> Self::Output { if rhs == 0.0_f64 { return Err(CommonTypeError::ConversionError { message: "Cannot divide price by zero".to_owned(), }); } Self::from_f64(self.to_f64() / rhs) } } impl From for Price { fn from(decimal: Decimal) -> Self { let f64_val: f64 = TryInto::::try_into(decimal).unwrap_or_else(|_| { tracing::warn!("Failed to convert Decimal to f64, using 0.0 as fallback"); 0.0_f64 }); Self::from_f64(f64_val).unwrap_or_else(|_| { tracing::warn!( "Failed to create Price from f64 value {}, using ZERO", f64_val ); Self::ZERO }) } } impl From for Decimal { fn from(price: Price) -> Self { price.to_decimal().unwrap_or(Decimal::ZERO) } } // 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 // ============================================================================= // Decimal Extension Trait // ============================================================================= impl Mul for Price { #[allow(clippy::float_arithmetic)] #[allow(clippy::arithmetic_side_effects)] type Output = Result; fn mul(self, rhs: Self) -> Self::Output { Self::from_f64(self.to_f64() * rhs.to_f64()) } } 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 { #[allow(clippy::float_arithmetic)] fn eq(&self, other: &f64) -> bool { (self.to_f64() - other).abs() < f64::EPSILON } } impl PartialEq for f64 { #[allow(clippy::float_arithmetic)] fn eq(&self, other: &Price) -> bool { (self - other.to_f64()).abs() < f64::EPSILON } } impl AddAssign for Price { fn add_assign(&mut self, rhs: Self) { self.value = self.value.saturating_add(rhs.value); } } impl SubAssign for Price { fn sub_assign(&mut self, rhs: Self) { self.value = self.value.saturating_sub(rhs.value); } } impl MulAssign for Price { fn mul_assign(&mut self, rhs: f64) { if let Ok(result) = self.mul(rhs) { *self = result; } // If multiplication fails, self remains unchanged } } impl DivAssign for Price { fn div_assign(&mut self, rhs: f64) { if let Ok(result) = self.div(rhs) { *self = result; } // If division fails, self remains unchanged } } 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 { value: u64, } impl Quantity { /// Zero quantity constant pub const ZERO: Self = Self { value: 0 }; /// One unit quantity constant pub const ONE: Self = Self { value: 100_000_000 }; /// Maximum possible quantity pub const MAX: Self = Self { value: u64::MAX }; /// Create a Quantity from a floating point value #[allow(clippy::as_conversions)] /// /// # Errors /// Returns error if the operation fails #[allow(clippy::float_arithmetic)] pub fn from_f64(value: f64) -> Result { if value < 0.0_f64 || !value.is_finite() { return Err(CommonTypeError::InvalidQuantity { value: value.to_string(), reason: "Quantity validation failed".to_owned(), }); } Ok(Self { value: (value * 100_000_000.0).round() as u64, }) } /// Convert quantity to floating point representation #[must_use] #[allow(clippy::as_conversions)] #[allow(clippy::float_arithmetic)] pub fn to_f64(&self) -> f64 { self.value as f64 / 100_000_000.0 } /// Convert the quantity to a Decimal value /// /// # Errors /// Returns error if the operation fails pub fn to_decimal(&self) -> Result { Decimal::from_f64_retain(self.to_f64()).ok_or_else(|| CommonTypeError::InvalidQuantity { value: "0.0".to_owned(), reason: "Quantity to Decimal conversion failed".to_owned(), }) } /// Get the internal value representation #[must_use] pub const fn value(&self) -> u64 { self.value } /// Get the raw internal value representation #[must_use] pub const fn raw_value(&self) -> u64 { self.value } /// Convert quantity to u64 representation #[must_use] pub const fn as_u64(&self) -> u64 { self.value } /// Create quantity from raw u64 value #[must_use] pub const fn from_raw(value: u64) -> Self { Self { value } } /// Create new quantity from f64 value /// /// # Errors /// Returns error if the operation fails #[allow(clippy::as_conversions)] pub fn new(value: f64) -> Result { Self::from_f64(value) } /// Create zero quantity #[must_use] pub const fn zero() -> Self { #[allow(clippy::as_conversions)] Self::ZERO } /// Create a quantity from an i64 value /// /// # Errors /// Returns error if the operation fails #[allow(clippy::as_conversions)] pub fn from_i64(value: i64) -> Result { Self::from_f64(value as f64) } /// Create a quantity from a u64 value /// /// # Errors #[allow(clippy::as_conversions)] /// Returns error if the operation fails pub fn from_u64(value: u64) -> Result { Self::from_f64(value as f64) } /// Create a quantity from a Decimal value /// /// # Errors /// Returns error if the operation fails pub fn from_decimal(decimal: Decimal) -> Result { use std::convert::TryFrom; Self::try_from(decimal).map_err(|e| CommonTypeError::InvalidQuantity { value: decimal.to_string(), reason: format!("Failed to convert Decimal to Quantity: {}", e), }) } /// Check if quantity is zero #[must_use] pub const fn is_zero(&self) -> bool { self.value == 0 } /// Check if quantity has a non-zero value #[must_use] pub const fn is_some(&self) -> bool { !self.is_zero() } /// Check if quantity is zero (none) #[must_use] pub const fn is_none(&self) -> bool { self.is_zero() } /// Get a reference to self #[must_use] pub const fn as_ref(&self) -> &Self { self } /// Get absolute value (always positive for Quantity) #[must_use] pub const fn abs(&self) -> Self { *self } /// Get the sign of the quantity (1.0 for positive, 0.0 for zero) #[must_use] pub const fn signum(&self) -> f64 { if self.value > 0 { 1.0 } else { 0.0 } } /// Check if quantity is positive #[must_use] pub const fn is_positive(&self) -> bool { self.value > 0 } /// Check if quantity is negative (always false for Quantity) #[must_use] pub const fn is_negative(&self) -> bool { false } /// Convert quantity to f64 representation #[must_use] pub fn as_f64(&self) -> f64 { self.to_f64() } /// Create quantity from number of shares #[must_use] pub const fn from_shares(shares: u64) -> Self { Self { value: shares.saturating_mul(100_000_000), } } /// Convert quantity to number of shares #[must_use] #[allow(clippy::integer_division)] pub const fn to_shares(&self) -> u64 { self.value / 100_000_000 } /// Multiply this quantity by another quantity /// /// # Errors /// Returns error if the operation fails #[allow(clippy::float_arithmetic)] #[allow(clippy::arithmetic_side_effects)] pub fn multiply(&self, other: Self) -> Result { Self::from_f64(self.to_f64() * other.to_f64()) } /// Subtract another quantity from this quantity #[must_use] pub fn subtract(&self, other: Self) -> Self { Self { value: self.value.checked_sub(other.value).unwrap_or_else(|| { tracing::warn!( "Quantity subtraction underflow: {} - {}, returning 0", self.value, other.value ); 0 }), } } /// Checked addition with overflow protection #[must_use] pub const fn checked_add(&self, other: Self) -> Option { match self.value.checked_add(other.value) { Some(value) => Some(Self { value }), None => None, } } /// Checked subtraction with underflow protection #[must_use] pub const fn checked_sub(&self, other: Self) -> Option { match self.value.checked_sub(other.value) { Some(value) => Some(Self { value }), None => None, } } } impl Default for Quantity { fn default() -> Self { Self::ZERO } } impl FromStr for Quantity { type Err = CommonTypeError; fn from_str(s: &str) -> Result { let parsed_value = s .parse::() .map_err(|e| CommonTypeError::InvalidQuantity { value: s.to_owned(), reason: format!("Cannot parse '{}' as quantity: {}", s, e), })?; Self::from_f64(parsed_value) } } impl fmt::Display for Quantity { /// Format the quantity for display with 8 decimal places fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:.8}", self.to_f64()) } } impl TryFrom for Quantity { type Error = CommonTypeError; fn try_from(value: i32) -> Result { Self::new(f64::from(value)) } } impl TryFrom for Quantity { type Error = CommonTypeError; fn try_from(value: u64) -> Result { #[allow(clippy::as_conversions)] let f64_value = value as f64; Self::new(f64_value) } } impl TryFrom for Quantity { type Error = CommonTypeError; fn try_from(value: f64) -> Result { Self::new(value) } } impl TryFrom for Quantity { type Error = CommonTypeError; fn try_from(decimal: Decimal) -> Result { let f64_val: f64 = TryInto::::try_into(decimal).map_err(|e| CommonTypeError::ConversionError { message: format!("Failed to convert Decimal to f64: {}", e), })?; Self::from_f64(f64_val) } } 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 { #[allow(clippy::float_arithmetic)] fn eq(&self, other: &f64) -> bool { (self.to_f64() - other).abs() < f64::EPSILON } } impl PartialEq for f64 { #[allow(clippy::float_arithmetic)] 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 { 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 { #[allow(clippy::float_arithmetic)] type Output = Result; fn mul(self, rhs: f64) -> Self::Output { Self::from_f64(self.to_f64() * rhs) } } impl Div for Quantity { #[allow(clippy::float_arithmetic)] type Output = Result; fn div(self, rhs: f64) -> Self::Output { if rhs == 0.0_f64 { return Err(CommonTypeError::ConversionError { message: "Cannot divide quantity by zero".to_owned(), }); } Self::from_f64(self.to_f64() / rhs) } } impl Sum for Quantity { fn sum>(iter: I) -> Self { iter.fold(Self::ZERO, |acc, x| Self { value: acc.value.saturating_add(x.value), }) } } impl<'quantity> Sum<&'quantity Self> for Quantity { fn sum>(iter: I) -> Self { iter.fold(Self::ZERO, |acc, x| Self { value: acc.value.saturating_add(x.value), }) } } // ============================================================================= // SQLX IMPLEMENTATIONS FOR FINANCIAL TYPES // ============================================================================= #[cfg(feature = "database")] mod sqlx_impls { use super::{HftTimestamp, MarketRegime, OrderSide, OrderStatus, OrderType, Price, Quantity}; use rust_decimal::Decimal as RustDecimal; use sqlx::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, postgres::{PgArgumentBuffer, PgTypeInfo, PgValueRef, Postgres}, Type, }; // SQLx implementations for Price impl Type for Price { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("NUMERIC") } } impl<'query> Encode<'query, Postgres> for Price { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // Convert our fixed-point u64 to rust_decimal::Decimal with 8 decimal places let decimal_value = RustDecimal::new(i64::try_from(self.raw_value()).unwrap_or(0), 8); decimal_value.encode_by_ref(buf) } } impl<'row> Decode<'row, Postgres> for Price { fn decode(value: PgValueRef<'row>) -> Result { // Decode from NUMERIC to rust_decimal::Decimal let decimal_value = >::decode(value)?; // Validate scale matches our fixed-point precision (8 decimal places) if decimal_value.scale() != 8 { return Err(format!( "Invalid scale for Price: expected 8, got {}", decimal_value.scale() ) .into()); } // Extract mantissa and convert to our u64 representation let mantissa = decimal_value.mantissa(); let inner_val = u64::try_from(mantissa).map_err(|e| { format!( "Failed to convert negative or overflowing NUMERIC to Price: {}", e ) })?; Ok(Price::from_raw(inner_val)) } } // SQLx implementations for Quantity impl Type for Quantity { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("NUMERIC") } } impl<'query> Encode<'query, Postgres> for Quantity { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // Convert our fixed-point u64 to rust_decimal::Decimal with 8 decimal places let decimal_value = RustDecimal::new(i64::try_from(self.raw_value()).unwrap_or(0), 8); decimal_value.encode_by_ref(buf) } } impl<'row> Decode<'row, Postgres> for Quantity { fn decode(value: PgValueRef<'row>) -> Result { // Decode from NUMERIC to rust_decimal::Decimal let decimal_value = >::decode(value)?; // Validate scale matches our fixed-point precision (8 decimal places) if decimal_value.scale() != 8 { return Err(format!( "Invalid scale for Quantity: expected 8, got {}", decimal_value.scale() ) .into()); } // Extract mantissa and convert to our u64 representation let mantissa = decimal_value.mantissa(); let inner_val = u64::try_from(mantissa).map_err(|e| { format!( "Failed to convert negative or overflowing NUMERIC to Quantity: {}", e ) })?; Ok(Quantity::from_raw(inner_val)) } } // SQLx implementations for TimeInForce impl Type for super::TimeInForce { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("TEXT") } } impl<'query> Encode<'query, Postgres> for super::TimeInForce { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // Use the Display trait to convert enum to string representation <&str as Encode>::encode(self.to_string().as_str(), buf) } } impl<'row> Decode<'row, Postgres> for super::TimeInForce { fn decode(value: PgValueRef<'row>) -> Result { // Decode from TEXT to string, then parse to enum let s = <&str as Decode>::decode(value)?; match s { "DAY" => Ok(super::TimeInForce::Day), "GTC" => Ok(super::TimeInForce::GoodTillCancel), "IOC" => Ok(super::TimeInForce::ImmediateOrCancel), "FOK" => Ok(super::TimeInForce::FillOrKill), _ => Err(format!("Invalid TimeInForce value: {}", s).into()), } } } // SQLx implementations for OrderStatus impl Type for OrderStatus { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("TEXT") } } impl<'query> Encode<'query, Postgres> for OrderStatus { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { let value = match self { OrderStatus::Created => "CREATED", OrderStatus::Submitted => "SUBMITTED", OrderStatus::PartiallyFilled => "PARTIALLY_FILLED", OrderStatus::Filled => "FILLED", OrderStatus::Rejected => "REJECTED", OrderStatus::Cancelled => "CANCELLED", OrderStatus::New => "NEW", OrderStatus::Expired => "EXPIRED", OrderStatus::Pending => "PENDING", OrderStatus::Working => "WORKING", OrderStatus::Unknown => "UNKNOWN", OrderStatus::Suspended => "SUSPENDED", OrderStatus::PendingCancel => "PENDING_CANCEL", OrderStatus::PendingReplace => "PENDING_REPLACE", }; <&str as Encode>::encode_by_ref(&value, buf) } } impl<'row> Decode<'row, Postgres> for OrderStatus { fn decode(value: PgValueRef<'row>) -> Result { let s = >::decode(value)?; match s.as_str() { "CREATED" => Ok(OrderStatus::Created), "SUBMITTED" => Ok(OrderStatus::Submitted), "PARTIALLY_FILLED" => Ok(OrderStatus::PartiallyFilled), "FILLED" => Ok(OrderStatus::Filled), "REJECTED" => Ok(OrderStatus::Rejected), "CANCELLED" => Ok(OrderStatus::Cancelled), "NEW" => Ok(OrderStatus::New), "EXPIRED" => Ok(OrderStatus::Expired), "PENDING" => Ok(OrderStatus::Pending), "WORKING" => Ok(OrderStatus::Working), "UNKNOWN" => Ok(OrderStatus::Unknown), "SUSPENDED" => Ok(OrderStatus::Suspended), "PENDING_CANCEL" => Ok(OrderStatus::PendingCancel), "PENDING_REPLACE" => Ok(OrderStatus::PendingReplace), _ => Err(format!("Invalid OrderStatus value: {}", s).into()), } } } // SQLx implementations for OrderSide impl Type for OrderSide { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("TEXT") } } impl<'query> Encode<'query, Postgres> for OrderSide { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { let value = match self { OrderSide::Buy => "BUY", OrderSide::Sell => "SELL", }; <&str as Encode>::encode_by_ref(&value, buf) } } impl<'row> Decode<'row, Postgres> for OrderSide { fn decode(value: PgValueRef<'row>) -> Result { let s = >::decode(value)?; match s.as_str() { "BUY" => Ok(OrderSide::Buy), "SELL" => Ok(OrderSide::Sell), _ => Err(format!("Invalid OrderSide value: {}", s).into()), } } } // SQLx implementations for OrderType impl Type for OrderType { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("TEXT") } } impl<'query> Encode<'query, Postgres> for OrderType { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { let value = match self { OrderType::Market => "MARKET", OrderType::Limit => "LIMIT", OrderType::Stop => "STOP", OrderType::StopLimit => "STOP_LIMIT", OrderType::Iceberg => "ICEBERG", OrderType::TrailingStop => "TRAILING_STOP", OrderType::Hidden => "HIDDEN", }; <&str as Encode>::encode_by_ref(&value, buf) } } impl<'row> Decode<'row, Postgres> for OrderType { fn decode(value: PgValueRef<'row>) -> Result { let s = >::decode(value)?; match s.as_str() { "MARKET" => Ok(OrderType::Market), "LIMIT" => Ok(OrderType::Limit), "STOP" => Ok(OrderType::Stop), "STOP_LIMIT" => Ok(OrderType::StopLimit), "ICEBERG" => Ok(OrderType::Iceberg), "TRAILING_STOP" => Ok(OrderType::TrailingStop), "HIDDEN" => Ok(OrderType::Hidden), _ => Err(format!("Invalid OrderType value: {}", s).into()), } } } // SQLx implementations for MarketRegime impl Type for MarketRegime { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("TEXT") } } impl<'query> Encode<'query, Postgres> for MarketRegime { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { let value = match self { MarketRegime::Normal => "NORMAL", MarketRegime::Crisis => "CRISIS", MarketRegime::Trending => "TRENDING", MarketRegime::Sideways => "SIDEWAYS", MarketRegime::Bull => "BULL", MarketRegime::Bear => "BEAR", MarketRegime::HighVolatility => "HIGH_VOLATILITY", MarketRegime::LowVolatility => "LOW_VOLATILITY", MarketRegime::Volatile => "VOLATILE", MarketRegime::Calm => "CALM", MarketRegime::Unknown => "UNKNOWN", MarketRegime::Recovery => "RECOVERY", MarketRegime::Bubble => "BUBBLE", MarketRegime::Correction => "CORRECTION", MarketRegime::Custom(id) => { return >::encode_by_ref( &format!("CUSTOM_{}", id), buf, ) }, }; <&str as Encode>::encode_by_ref(&value, buf) } } impl<'row> Decode<'row, Postgres> for MarketRegime { fn decode(value: PgValueRef<'row>) -> Result { let s = >::decode(value)?; match s.as_str() { "NORMAL" => Ok(MarketRegime::Normal), "CRISIS" => Ok(MarketRegime::Crisis), "TRENDING" => Ok(MarketRegime::Trending), "SIDEWAYS" => Ok(MarketRegime::Sideways), "BULL" => Ok(MarketRegime::Bull), "BEAR" => Ok(MarketRegime::Bear), "HIGH_VOLATILITY" => Ok(MarketRegime::HighVolatility), "LOW_VOLATILITY" => Ok(MarketRegime::LowVolatility), "VOLATILE" => Ok(MarketRegime::Volatile), "CALM" => Ok(MarketRegime::Calm), "UNKNOWN" => Ok(MarketRegime::Unknown), "RECOVERY" => Ok(MarketRegime::Recovery), "BUBBLE" => Ok(MarketRegime::Bubble), "CORRECTION" => Ok(MarketRegime::Correction), _ => { // Handle Custom(id) format if let Some(id_str) = s.strip_prefix("CUSTOM_") { if let Ok(id) = id_str.parse::() { Ok(MarketRegime::Custom(id)) } else { Err(format!("Invalid MarketRegime Custom ID: {}", id_str).into()) } } else { Err(format!("Invalid MarketRegime value: {}", s).into()) } }, } } } // SQLx implementations for HftTimestamp // Maps to PostgreSQL BIGINT (stores nanoseconds since Unix epoch) // Note: Limited to i64::MAX nanoseconds (year 2262) due to PostgreSQL BIGINT constraints impl<'query> Encode<'query, Postgres> for HftTimestamp { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // Cast u64 to i64 for PostgreSQL BIGINT compatibility >::encode(i64::try_from(self.nanos()).unwrap_or(0), buf) } } impl<'row> Decode<'row, Postgres> for HftTimestamp { fn decode(value: PgValueRef<'row>) -> Result { let val = >::decode(value)?; // Cast i64 back to u64 for internal representation Ok(HftTimestamp::from_nanos(u64::try_from(val).unwrap_or(0))) } } impl Type for HftTimestamp { fn type_info() -> ::TypeInfo { >::type_info() } fn compatible(ty: &::TypeInfo) -> bool { >::compatible(ty) } } // SQLx implementations for OrderId (uses BIGINT for u64) impl Type for super::OrderId { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("BIGINT") } } impl<'query> Encode<'query, Postgres> for super::OrderId { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { >::encode_by_ref( &(i64::try_from(self.value()).unwrap_or(0)), buf, ) } } impl<'row> Decode<'row, Postgres> for super::OrderId { fn decode(value: PgValueRef<'row>) -> Result { let id = >::decode(value)?; Ok(super::OrderId::from_u64(u64::try_from(id).unwrap_or(0))) } } } /// Volume type - alias for Quantity with the same fixed-point arithmetic /// /// `SQLx` traits are automatically inherited from Quantity pub type Volume = Quantity; // ORDER TYPES ALREADY DEFINED ABOVE - No need to re-export from trading_engine // ============================================================================= // CORE ID TYPES (MOVED FROM TRADING_ENGINE) // ============================================================================= /// Order identifier with ultra-fast atomic generation /// /// Replaces slow UUID generation (1ms+) with atomic increment (~5ns) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct OrderId(u64); impl Default for OrderId { fn default() -> Self { Self::new() } } impl OrderId { /// Generate next `OrderId` using atomic counter - <50ns performance pub fn new() -> Self { use std::sync::atomic::{AtomicU64, Ordering}; static COUNTER: AtomicU64 = AtomicU64::new(1); Self(COUNTER.fetch_add(1, Ordering::Relaxed)) } /// Create `OrderId` from u64 value #[must_use] pub const fn from_u64(value: u64) -> Self { Self(value) } /// Get u64 value #[must_use] pub const fn value(&self) -> u64 { self.0 } /// Get u64 value for performance-critical code (alias for value) #[must_use] pub const fn as_u64(&self) -> u64 { self.0 } /// Get as string for compatibility #[must_use] pub fn as_str(&self) -> String { self.0.to_string() } } impl fmt::Display for OrderId { /// Format the order ID for display fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl From for OrderId { /// Create an `OrderId` from a u64 value fn from(value: u64) -> Self { Self(value) } } impl From for u64 { /// Convert an `OrderId` to u64 fn from(order_id: OrderId) -> Self { order_id.0 } } impl FromStr for OrderId { type Err = ParseIntError; fn from_str(s: &str) -> Result { s.parse::().map(OrderId) } } impl From for OrderId { /// Create an `OrderId` from a String, generating new ID if parsing fails fn from(s: String) -> Self { s.parse().unwrap_or_else(|_| Self::new()) } } impl From<&str> for OrderId { /// Create an `OrderId` from a &str, generating new ID if parsing fails fn from(s: &str) -> Self { s.parse().unwrap_or_else(|_| Self::new()) } } /// Execution identifier with validation #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[cfg_attr(feature = "database", derive(sqlx::Type))] pub struct ExecutionId(String); impl ExecutionId { /// Create a new execution ID with validation /// /// # Errors /// Returns error if the operation fails pub fn new>(id: S) -> Result { let id = id.into(); if id.trim().is_empty() { return Err(CommonTypeError::ValidationError { field: "execution_id".to_owned(), reason: "Execution ID cannot be empty".to_owned(), }); } Ok(Self(id)) } /// Generate a new random execution ID pub fn generate() -> Self { Self(uuid::Uuid::new_v4().to_string()) } /// Get execution ID as string slice pub fn as_str(&self) -> &str { &self.0 } /// Convert execution ID into owned string pub fn into_string(self) -> String { self.0 } } impl fmt::Display for ExecutionId { /// Format the execution ID for display fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl FromStr for ExecutionId { type Err = CommonTypeError; fn from_str(s: &str) -> Result { Self::new(s) } } /// Trade identifier with validation #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct TradeId(String); impl TradeId { /// Create a new trade ID with validation /// /// # Errors /// Returns error if the operation fails pub fn new>(id: S) -> Result { let id = id.into(); if id.is_empty() { return Err(CommonTypeError::ValidationError { field: "trade_id".to_owned(), reason: "Trade ID cannot be empty".to_owned(), }); } Ok(Self(id)) } /// Get the trade ID as a string slice pub fn as_str(&self) -> &str { &self.0 } /// Convert the trade ID into an owned string pub fn into_string(self) -> String { self.0 } } impl fmt::Display for TradeId { /// Format the trade ID for display fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } /// Trading symbol with validation #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[cfg_attr(feature = "database", derive(sqlx::Type))] pub struct Symbol { value: String, } impl Symbol { /// Create a new symbol from a string #[must_use] pub const fn new(s: String) -> Self { Self { value: s } } /// Create a new Symbol with validation /// /// # Errors /// Returns error if the operation fails pub fn new_validated(s: String) -> Result { if s.trim().is_empty() { return Err(CommonTypeError::ValidationError { field: "symbol".to_owned(), reason: "Symbol cannot be empty".to_owned(), }); } Ok(Self { value: s }) } /// Create a Symbol from &str with validation /// /// # Errors /// Returns error if the operation fails pub fn from_str_validated(s: &str) -> Result { Self::new_validated(s.to_owned()) } /// Get the symbol as a string slice #[must_use] pub fn as_str(&self) -> &str { &self.value } /// Get the symbol value as a string slice #[must_use] pub fn value(&self) -> &str { &self.value } /// Get the symbol as bytes #[must_use] pub fn as_bytes(&self) -> &[u8] { self.value.as_bytes() } /// Check if the symbol is empty #[must_use] pub fn is_empty(&self) -> bool { self.value.is_empty() } /// Convert the symbol to uppercase #[must_use] pub fn to_uppercase(&self) -> String { self.value.to_uppercase() } /// Replace occurrences in the symbol #[must_use] pub fn replace(&self, from: &str, to: &str) -> String { self.value.replace(from, to) } /// Helper for risk management - creates a 'NONE' symbol #[must_use] pub fn none() -> Self { "NONE".parse().unwrap() } /// Check if the symbol contains a pattern #[must_use] pub fn contains(&self, pattern: &str) -> bool { self.value.contains(pattern) } } impl FromStr for Symbol { type Err = std::convert::Infallible; fn from_str(s: &str) -> Result { Ok(Self { value: s.to_owned(), }) } } // Additional implementation to support conversion from &Symbol to &str impl AsRef for Symbol { /// Convert symbol to string reference fn as_ref(&self) -> &str { &self.value } } impl fmt::Display for Symbol { /// Format the symbol for display fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.value) } } impl From for Symbol { /// Create a Symbol from a String fn from(s: String) -> Self { Self::new(s) } } impl From<&str> for Symbol { /// Create a Symbol from a &str fn from(s: &str) -> Self { Self::new(s.to_owned()) } } // TryFrom implementations removed due to conflicting blanket implementations // Use Symbol::new_validated() or Symbol::from_validated() directly instead impl Default for Symbol { /// Returns the default symbol (empty string) fn default() -> Self { Self::new(String::new()) } } impl PartialEq for Symbol { fn eq(&self, other: &str) -> bool { self.value == other } } impl PartialEq<&str> 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: common::types::TimeInForce // Currency moved to canonical source: common::types::Currency // Price moved to canonical source: common::types::Price // Quantity moved to canonical source: common::types::Quantity // Volume moved to canonical source: common::types::Quantity (as Volume alias) /// Money amount with currency #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Money { /// The monetary amount pub amount: Decimal, /// The currency of the amount pub currency: Currency, } impl Money { /// Create new money amount pub const fn new(amount: Decimal, currency: Currency) -> Self { Self { amount, currency } } } impl fmt::Display for Money { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} {}", self.amount, self.currency) } } // OrderId moved to canonical source: common::types::OrderId // TradeId moved to canonical source: common::types::TradeId // Symbol moved to canonical source: common::types::Symbol /// Type-safe account identifier #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct AccountId(String); impl AccountId { /// Create a new account ID with validation /// /// # Errors /// Returns error if the operation fails pub fn new>(id: S) -> Result { let id = id.into(); if id.trim().is_empty() { return Err(CommonTypeError::InvalidIdentifier { field: "account_id".to_owned(), reason: "Account ID cannot be empty".to_owned(), }); } Ok(Self(id)) } /// Get the ID as a string slice pub fn as_str(&self) -> &str { &self.0 } /// Convert to owned String pub fn into_string(self) -> String { self.0 } } impl fmt::Display for AccountId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } /// High-precision timestamp for HFT applications - CANONICAL DEFINITION /// /// Robust implementation with error handling for financial safety #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default, )] pub struct HftTimestamp { nanos: u64, } impl HftTimestamp { /// Get current timestamp with error handling for financial safety /// /// # Errors /// Returns error if the operation fails pub fn now() -> Result { use std::time::{SystemTime, UNIX_EPOCH}; let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|e| CommonError::Service { category: CommonErrorCategory::System, message: format!("System time before UNIX epoch: {e}"), })? .as_nanos() .try_into() .unwrap_or(0_u64); Ok(Self { nanos }) } /// Get current timestamp with error handling for financial safety (`CommonTypeError` version) /// /// # Errors /// Returns error if the operation fails pub fn now_common() -> Result { use std::time::{SystemTime, UNIX_EPOCH}; let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|e| CommonTypeError::ConversionError { message: format!("System time before UNIX epoch: {e}"), })? .as_nanos() .try_into() .unwrap_or(0_u64); Ok(Self { nanos }) } /// Get current timestamp or zero if system time is invalid #[must_use] pub fn now_or_zero() -> Self { Self::now().unwrap_or(Self { nanos: 0 }) } /// Get nanoseconds since epoch #[must_use] pub const fn nanos(self) -> u64 { self.nanos } #[allow(clippy::as_conversions)] /// Create from nanoseconds since epoch #[must_use] pub const fn from_nanos(nanos: u64) -> Self { Self { nanos } } /// Create from signed nanoseconds (cast to unsigned) #[must_use] #[allow(clippy::as_conversions)] pub const fn from_nanos_i64(nanos: i64) -> Self { Self { nanos: nanos as u64, } } /// Get nanoseconds since epoch pub const fn as_nanos(&self) -> u64 { self.nanos } /// Convert to `DateTime` #[allow(clippy::integer_division)] pub fn to_datetime(&self) -> DateTime { let secs = self.nanos / 1_000_000_000; let nsecs = u32::try_from(self.nanos % 1_000_000_000).unwrap_or(0); DateTime::from_timestamp(i64::try_from(secs).unwrap_or(0), nsecs).unwrap_or_default() } } impl fmt::Display for HftTimestamp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.to_datetime()) } } /// Generic timestamp for general use cases #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct GenericTimestamp { nanos: u64, } impl GenericTimestamp { /// Create from nanoseconds since epoch #[must_use] pub const fn from_nanos(nanos: u64) -> Self { Self { nanos } } /// Get nanoseconds since epoch #[must_use] pub const fn nanos(&self) -> u64 { self.nanos } } // ============================================================================= // MARKET TYPES (MIGRATED FROM TRADING_ENGINE) // ============================================================================= /// Market regime enumeration for position sizing scaling and risk management #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum MarketRegime { /// Normal market conditions Normal, /// Crisis/stress market conditions Crisis, /// Trending market (strong directional movement) Trending, /// Sideways/ranging market (low volatility) Sideways, /// Bull market (sustained upward trend) Bull, /// Bear market (sustained downward trend) Bear, /// High volatility market conditions HighVolatility, /// Low volatility market conditions LowVolatility, /// Volatile market conditions (alias for `HighVolatility`) Volatile, /// Calm market conditions (alias for `LowVolatility`) Calm, /// Unknown/unclassified regime Unknown, /// Recovery regime - transitioning from crisis Recovery, /// Bubble regime - unsustainable upward movement Bubble, /// Correction regime - temporary downward adjustment Correction, /// Custom regime with numeric identifier Custom(usize), } impl Default for MarketRegime { fn default() -> Self { Self::Normal } } impl fmt::Display for MarketRegime { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Normal => write!(f, "Normal"), Self::Crisis => write!(f, "Crisis"), Self::Trending => write!(f, "Trending"), Self::Sideways => write!(f, "Sideways"), Self::Bull => write!(f, "Bull"), Self::Bear => write!(f, "Bear"), Self::HighVolatility => write!(f, "HighVolatility"), Self::LowVolatility => write!(f, "LowVolatility"), Self::Volatile => write!(f, "Volatile"), Self::Calm => write!(f, "Calm"), Self::Unknown => write!(f, "Unknown"), Self::Recovery => write!(f, "Recovery"), Self::Bubble => write!(f, "Bubble"), Self::Correction => write!(f, "Correction"), Self::Custom(id) => write!(f, "Custom({id})"), } } } /// Tick type enumeration for market data #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[cfg_attr(feature = "database", derive(sqlx::Type))] #[cfg_attr( feature = "database", sqlx(type_name = "tick_type", rename_all = "snake_case") )] pub enum TickType { /// Trade execution tick Trade, /// Bid price update tick Bid, /// Ask price update tick Ask, /// Quote (bid/ask) update tick Quote, } /// Exchange enumeration for trading venues #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Exchange { /// New York Stock Exchange NYSE, /// NASDAQ NASDAQ, /// Chicago Mercantile Exchange CME, /// Intercontinental Exchange ICE, /// London Stock Exchange LSE, /// Tokyo Stock Exchange TSE, /// Hong Kong Stock Exchange HKEX, /// Shanghai Stock Exchange SSE, /// Shenzhen Stock Exchange SZSE, /// Euronext EURONEXT, /// Deutsche Börse XETRA, /// Chicago Board of Trade CBOT, /// Chicago Board Options Exchange CBOE, /// BATS Global Markets BATS, /// IEX Exchange IEX, /// Interactive Brokers IBKR, /// IC Markets ICMARKETS, /// Forex.com FOREX, /// Binance BINANCE, /// Coinbase COINBASE, /// Kraken KRAKEN, /// Unknown or unrecognized exchange UNKNOWN, } impl Default for Exchange { fn default() -> Self { Self::UNKNOWN } } impl fmt::Display for Exchange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NYSE => write!(f, "NYSE"), Self::NASDAQ => write!(f, "NASDAQ"), Self::CME => write!(f, "CME"), Self::ICE => write!(f, "ICE"), Self::LSE => write!(f, "LSE"), Self::TSE => write!(f, "TSE"), Self::HKEX => write!(f, "HKEX"), Self::SSE => write!(f, "SSE"), Self::SZSE => write!(f, "SZSE"), Self::EURONEXT => write!(f, "EURONEXT"), Self::XETRA => write!(f, "XETRA"), Self::CBOT => write!(f, "CBOT"), Self::CBOE => write!(f, "CBOE"), Self::BATS => write!(f, "BATS"), Self::IEX => write!(f, "IEX"), Self::IBKR => write!(f, "IBKR"), Self::ICMARKETS => write!(f, "ICMARKETS"), Self::FOREX => write!(f, "FOREX"), Self::BINANCE => write!(f, "BINANCE"), Self::COINBASE => write!(f, "COINBASE"), Self::KRAKEN => write!(f, "KRAKEN"), Self::UNKNOWN => write!(f, "UNKNOWN"), } } } impl FromStr for Exchange { type Err = CommonTypeError; fn from_str(s: &str) -> Result { match s.to_uppercase().as_str() { "NYSE" => Ok(Self::NYSE), "NASDAQ" => Ok(Self::NASDAQ), "CME" => Ok(Self::CME), "ICE" => Ok(Self::ICE), "LSE" => Ok(Self::LSE), "TSE" => Ok(Self::TSE), "HKEX" => Ok(Self::HKEX), "SSE" => Ok(Self::SSE), "SZSE" => Ok(Self::SZSE), "EURONEXT" => Ok(Self::EURONEXT), "XETRA" => Ok(Self::XETRA), "CBOT" => Ok(Self::CBOT), "CBOE" => Ok(Self::CBOE), "BATS" => Ok(Self::BATS), "IEX" => Ok(Self::IEX), "IBKR" => Ok(Self::IBKR), "ICMARKETS" => Ok(Self::ICMARKETS), "FOREX" => Ok(Self::FOREX), "BINANCE" => Ok(Self::BINANCE), "COINBASE" => Ok(Self::COINBASE), "KRAKEN" => Ok(Self::KRAKEN), "UNKNOWN" => Ok(Self::UNKNOWN), _ => Ok(Self::UNKNOWN), // Default to UNKNOWN for unrecognized exchanges } } } /// Market tick data structure - CANONICAL SINGLE SOURCE OF TRUTH #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MarketTick { /// Trading symbol pub symbol: Symbol, /// Tick price pub price: Price, /// Tick size/quantity pub size: Quantity, /// Tick timestamp pub timestamp: HftTimestamp, /// Type of tick (trade, bid, ask, quote) pub tick_type: TickType, /// Exchange where the tick occurred pub exchange: Exchange, /// Sequence number for ordering pub sequence_number: u64, } impl MarketTick { /// Create a new market tick with current timestamp /// /// # Errors /// Returns error if the operation fails pub fn new( symbol: Symbol, price: Price, size: Quantity, tick_type: TickType, exchange: Exchange, sequence_number: u64, ) -> Result { Ok(Self { symbol, price, size, timestamp: HftTimestamp::now()?, tick_type, exchange, sequence_number, }) } /// Create a new market tick with specified timestamp (for backtesting) #[must_use] pub const fn with_timestamp( symbol: Symbol, price: Price, size: Quantity, timestamp: HftTimestamp, tick_type: TickType, exchange: Exchange, sequence_number: u64, ) -> Self { Self { symbol, price, size, timestamp, tick_type, exchange, sequence_number, } } } /// Trading signal for algorithmic trading #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct TradingSignal { /// Signal ID pub signal_id: Uuid, /// Symbol this signal applies to pub symbol: Symbol, /// Signal strength (-1.0 to 1.0) pub strength: f64, /// Signal direction pub direction: OrderSide, /// Confidence level (0.0 to 1.0) pub confidence: f64, /// Signal generation timestamp pub timestamp: HftTimestamp, /// Signal source/strategy pub source: String, /// Additional metadata pub metadata: std::collections::HashMap, } impl TradingSignal { /// Create a new trading signal /// /// # Errors /// Returns error if the operation fails pub fn new( symbol: Symbol, strength: f64, direction: OrderSide, confidence: f64, source: String, ) -> Result { if !(0.0_f64..=1.0_f64).contains(&confidence) { return Err(CommonTypeError::ValidationError { field: "confidence".to_owned(), reason: "Confidence must be between 0.0 and 1.0".to_owned(), }); } if !(-1.0_f64..=1.0_f64).contains(&strength) { return Err(CommonTypeError::ValidationError { field: "strength".to_owned(), reason: "Strength must be between -1.0 and 1.0".to_owned(), }); } Ok(Self { signal_id: Uuid::new_v4(), symbol, strength, direction, confidence, timestamp: HftTimestamp::now_common()?, source, metadata: std::collections::HashMap::new(), }) } /// Add metadata to the signal #[must_use] pub fn with_metadata(mut self, key: String, value: String) -> Self { self.metadata.insert(key, value); self } } // ============================================================================= // HIGH-PERFORMANCE TYPES FOR COPY/CLONE OPTIMIZATION // ============================================================================= /// Lightweight Order reference for high-performance contexts requiring Copy trait /// /// This struct contains only the essential order data needed for performance-critical /// operations like `SmallBatchRing` processing, while maintaining Copy semantics. /// /// For full order details, use the complete Order struct. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct OrderRef { /// Order ID (u64 for performance) pub id: u64, /// Symbol hash for fast lookups pub symbol_hash: i64, /// Order side (Buy/Sell) pub side: OrderSide, /// Order type pub order_type: OrderType, /// Quantity (fixed-point u64) pub quantity: u64, /// Price (fixed-point u64, 0 for market orders) pub price: u64, /// Timestamp (nanoseconds since epoch) pub timestamp: u64, } impl OrderRef { /// Create `OrderRef` from a full Order struct #[must_use] pub fn from_order(order: &Order) -> Self { Self { id: order.id.value(), symbol_hash: order.symbol_hash(), side: order.side, order_type: order.order_type, quantity: order.quantity.raw_value(), price: order.price.map_or(0, |p| p.raw_value()), timestamp: order.created_at.nanos(), } } /// Create a limit order reference #[must_use] pub fn limit(symbol_hash: i64, side: OrderSide, quantity: u64, price: u64) -> Self { Self { id: OrderId::new().value(), symbol_hash, side, order_type: OrderType::Limit, quantity, price, timestamp: HftTimestamp::now_or_zero().nanos(), } } /// Create a market order reference #[must_use] pub fn market(symbol_hash: i64, side: OrderSide, quantity: u64) -> Self { Self { id: OrderId::new().value(), symbol_hash, side, order_type: OrderType::Market, quantity, price: 0, timestamp: HftTimestamp::now_or_zero().nanos(), } } /// Get quantity as Quantity type #[must_use] pub const fn get_quantity(&self) -> Quantity { Quantity::from_raw(self.quantity) } /// Get price as Price type (None for market orders) #[must_use] pub const fn get_price(&self) -> Option { if self.price == 0 { None } else { Some(Price::from_raw(self.price)) } } /// Check if this is a buy order #[must_use] pub fn is_buy(&self) -> bool { self.side == OrderSide::Buy } /// Check if this is a sell order #[must_use] pub fn is_sell(&self) -> bool { self.side == OrderSide::Sell } /// Check if this is a market order #[must_use] pub fn is_market_order(&self) -> bool { self.order_type == OrderType::Market || self.price == 0 } /// Check if this is a limit order #[must_use] pub fn is_limit_order(&self) -> bool { self.order_type == OrderType::Limit && self.price > 0 } } impl Default for OrderRef { fn default() -> Self { Self { id: 0, symbol_hash: 0, side: OrderSide::Buy, order_type: OrderType::Market, quantity: 0, price: 0, timestamp: 0, } } } // ============================================================================= // COMPREHENSIVE TESTS // ============================================================================= #[cfg(test)] mod tests { use super::*; use std::str::FromStr; // ============================================================================= // Price Tests // ============================================================================= #[test] fn test_price_from_f64_valid() { let price = Price::from_f64(100.50).unwrap(); assert_eq!(price.to_f64(), 100.50); } #[test] fn test_price_from_f64_negative() { let result = Price::from_f64(-10.0); assert!(result.is_err()); } #[test] fn test_price_from_f64_nan() { let result = Price::from_f64(f64::NAN); assert!(result.is_err()); } #[test] fn test_price_from_f64_infinity() { let result = Price::from_f64(f64::INFINITY); assert!(result.is_err()); } #[test] fn test_price_constants() { assert_eq!(Price::ZERO.to_f64(), 0.0); assert_eq!(Price::ONE.to_f64(), 1.0); assert_eq!(Price::CENT.to_f64(), 0.01); } #[test] fn test_price_addition() { let p1 = Price::from_f64(10.0).unwrap(); let p2 = Price::from_f64(5.5).unwrap(); let result = p1 + p2; assert!((result.to_f64() - 15.5).abs() < 0.00001); } #[test] fn test_price_subtraction() { let p1 = Price::from_f64(10.0).unwrap(); let p2 = Price::from_f64(5.5).unwrap(); let result = p1 - p2; assert!((result.to_f64() - 4.5).abs() < 0.00001); } #[test] fn test_price_multiplication() { let price = Price::from_f64(10.0).unwrap(); let result = (price * 2.5).unwrap(); assert!((result.to_f64() - 25.0).abs() < 0.00001); } #[test] fn test_price_division() { let price = Price::from_f64(10.0).unwrap(); let result = (price / 2.0).unwrap(); assert!((result.to_f64() - 5.0).abs() < 0.00001); } #[test] fn test_price_division_by_zero() { let price = Price::from_f64(10.0).unwrap(); let result = price / 0.0; assert!(result.is_err()); } #[test] fn test_price_from_cents() { let price = Price::from_cents(150); assert!((price.to_f64() - 1.50).abs() < 0.00001); } #[test] fn test_price_to_cents() { let price = Price::from_f64(1.50).unwrap(); assert_eq!(price.to_cents(), 150); } #[test] fn test_price_is_zero() { assert!(Price::ZERO.is_zero()); assert!(!Price::from_f64(1.0).unwrap().is_zero()); } #[test] fn test_price_from_str() { let price = Price::from_str("123.45").unwrap(); assert!((price.to_f64() - 123.45).abs() < 0.00001); } #[test] fn test_price_from_str_invalid() { let result = Price::from_str("invalid"); assert!(result.is_err()); } #[test] fn test_price_display() { let price = Price::from_f64(123.456789).unwrap(); let display = format!("{}", price); assert!(display.starts_with("123.45678")); } #[test] fn test_price_partial_eq_f64() { let price = Price::from_f64(10.0).unwrap(); assert_eq!(price, 10.0); assert_eq!(10.0, price); } #[test] fn test_price_multiply_price() { let p1 = Price::from_f64(10.0).unwrap(); let p2 = Price::from_f64(2.5).unwrap(); let result = p1.multiply(p2).unwrap(); assert!((result.to_f64() - 25.0).abs() < 0.00001); } // ============================================================================= // Quantity Tests // ============================================================================= #[test] fn test_quantity_from_f64_valid() { let qty = Quantity::from_f64(100.5).unwrap(); assert_eq!(qty.to_f64(), 100.5); } #[test] fn test_quantity_from_f64_negative() { let result = Quantity::from_f64(-10.0); assert!(result.is_err()); } #[test] fn test_quantity_from_f64_nan() { let result = Quantity::from_f64(f64::NAN); assert!(result.is_err()); } #[test] fn test_quantity_constants() { assert_eq!(Quantity::ZERO.to_f64(), 0.0); assert_eq!(Quantity::ONE.to_f64(), 1.0); } #[test] fn test_quantity_addition() { let q1 = Quantity::from_f64(10.0).unwrap(); let q2 = Quantity::from_f64(5.5).unwrap(); let result = q1 + q2; assert!((result.to_f64() - 15.5).abs() < 0.00001); } #[test] fn test_quantity_subtraction() { let q1 = Quantity::from_f64(10.0).unwrap(); let q2 = Quantity::from_f64(5.5).unwrap(); let result = q1 - q2; assert!((result.to_f64() - 4.5).abs() < 0.00001); } #[test] fn test_quantity_multiplication() { let qty = Quantity::from_f64(10.0).unwrap(); let result = (qty * 2.5).unwrap(); assert!((result.to_f64() - 25.0).abs() < 0.00001); } #[test] fn test_quantity_division() { let qty = Quantity::from_f64(10.0).unwrap(); let result = (qty / 2.0).unwrap(); assert!((result.to_f64() - 5.0).abs() < 0.00001); } #[test] fn test_quantity_division_by_zero() { let qty = Quantity::from_f64(10.0).unwrap(); let result = qty / 0.0; assert!(result.is_err()); } #[test] fn test_quantity_is_zero() { assert!(Quantity::ZERO.is_zero()); assert!(!Quantity::from_f64(1.0).unwrap().is_zero()); } #[test] fn test_quantity_is_positive() { assert!(Quantity::from_f64(1.0).unwrap().is_positive()); assert!(!Quantity::ZERO.is_positive()); } #[test] fn test_quantity_is_negative() { // Quantity is always non-negative assert!(!Quantity::from_f64(1.0).unwrap().is_negative()); assert!(!Quantity::ZERO.is_negative()); } #[test] fn test_quantity_from_shares() { let qty = Quantity::from_shares(100); assert_eq!(qty.to_shares(), 100); } #[test] fn test_quantity_sum() { let quantities = vec![ Quantity::from_f64(1.0).unwrap(), Quantity::from_f64(2.0).unwrap(), Quantity::from_f64(3.0).unwrap(), ]; let sum: Quantity = quantities.into_iter().sum(); assert!((sum.to_f64() - 6.0).abs() < 0.00001); } #[test] fn test_quantity_try_from_i32() { let qty = Quantity::try_from(100i32).unwrap(); assert_eq!(qty.to_f64(), 100.0); } #[test] fn test_quantity_try_from_string() { let qty = Quantity::try_from("123.45").unwrap(); assert!((qty.to_f64() - 123.45).abs() < 0.00001); } // ============================================================================= // Money Tests // ============================================================================= #[test] fn test_money_new() { let amount = Decimal::from_f64_retain(100.50).unwrap(); let money = Money::new(amount, Currency::USD); assert_eq!(money.currency, Currency::USD); assert_eq!(money.amount, amount); } #[test] fn test_money_display() { let amount = Decimal::from_f64_retain(100.50).unwrap(); let money = Money::new(amount, Currency::USD); let display = format!("{}", money); assert!(display.contains("100.5")); assert!(display.contains("USD")); } // ============================================================================= // Symbol Tests // ============================================================================= #[test] fn test_symbol_new() { let symbol = Symbol::new("AAPL".to_owned()); assert_eq!(symbol.as_str(), "AAPL"); } #[test] fn test_symbol_new_validated_valid() { let symbol = Symbol::new_validated("AAPL".to_owned()).unwrap(); assert_eq!(symbol.as_str(), "AAPL"); } #[test] fn test_symbol_new_validated_empty() { let result = Symbol::new_validated("".to_owned()); assert!(result.is_err()); } #[test] fn test_symbol_new_validated_whitespace() { let result = Symbol::new_validated(" ".to_owned()); assert!(result.is_err()); } #[test] fn test_symbol_from_str() { let symbol = Symbol::from_str("AAPL").unwrap(); assert_eq!(symbol.as_str(), "AAPL"); } #[test] fn test_symbol_to_uppercase() { let symbol = Symbol::from_str("aapl").unwrap(); assert_eq!(symbol.to_uppercase(), "AAPL"); } #[test] fn test_symbol_replace() { let symbol = Symbol::from_str("AAPL.US").unwrap(); assert_eq!(symbol.replace(".US", ""), "AAPL"); } #[test] fn test_symbol_contains() { let symbol = Symbol::from_str("AAPL.US").unwrap(); assert!(symbol.contains("AAPL")); assert!(!symbol.contains("MSFT")); } #[test] fn test_symbol_partial_eq_str() { let symbol = Symbol::from_str("AAPL").unwrap(); assert_eq!("AAPL", symbol); assert_eq!(symbol.as_str(), "AAPL"); } #[test] fn test_symbol_none() { let symbol = Symbol::none(); assert_eq!(symbol.as_str(), "NONE"); } // ============================================================================= // TimeInForce Tests // ============================================================================= #[test] fn test_time_in_force_display() { assert_eq!(format!("{}", TimeInForce::Day), "DAY"); assert_eq!(format!("{}", TimeInForce::GoodTillCancel), "GTC"); assert_eq!(format!("{}", TimeInForce::ImmediateOrCancel), "IOC"); assert_eq!(format!("{}", TimeInForce::FillOrKill), "FOK"); } #[test] fn test_time_in_force_default() { assert_eq!(TimeInForce::default(), TimeInForce::Day); } // ============================================================================= // OrderType Tests // ============================================================================= #[test] fn test_order_type_display() { assert_eq!(format!("{}", OrderType::Market), "MARKET"); assert_eq!(format!("{}", OrderType::Limit), "LIMIT"); assert_eq!(format!("{}", OrderType::Stop), "STOP"); assert_eq!(format!("{}", OrderType::StopLimit), "STOP_LIMIT"); } #[test] fn test_order_type_try_from_i32_valid() { assert_eq!(OrderType::try_from(0).unwrap(), OrderType::Market); assert_eq!(OrderType::try_from(1).unwrap(), OrderType::Limit); assert_eq!(OrderType::try_from(2).unwrap(), OrderType::Stop); } #[test] fn test_order_type_try_from_i32_invalid() { let result = OrderType::try_from(99); assert!(result.is_err()); } #[test] fn test_order_type_default() { assert_eq!(OrderType::default(), OrderType::Market); } // ============================================================================= // OrderStatus Tests // ============================================================================= #[test] fn test_order_status_display() { assert_eq!(format!("{}", OrderStatus::Created), "CREATED"); assert_eq!(format!("{}", OrderStatus::Filled), "FILLED"); assert_eq!(format!("{}", OrderStatus::Cancelled), "CANCELLED"); } #[test] fn test_order_status_try_from_i32_valid() { assert_eq!(OrderStatus::try_from(0).unwrap(), OrderStatus::Created); assert_eq!(OrderStatus::try_from(3).unwrap(), OrderStatus::Filled); assert_eq!(OrderStatus::try_from(5).unwrap(), OrderStatus::Cancelled); } #[test] fn test_order_status_try_from_i32_invalid() { let result = OrderStatus::try_from(99); assert!(result.is_err()); } // ============================================================================= // OrderSide Tests // ============================================================================= #[test] fn test_order_side_display() { assert_eq!(format!("{}", OrderSide::Buy), "BUY"); assert_eq!(format!("{}", OrderSide::Sell), "SELL"); } #[test] fn test_order_side_try_from_i32_valid() { assert_eq!(OrderSide::try_from(0).unwrap(), OrderSide::Buy); assert_eq!(OrderSide::try_from(1).unwrap(), OrderSide::Sell); } #[test] fn test_order_side_try_from_i32_invalid() { let result = OrderSide::try_from(99); assert!(result.is_err()); } #[test] fn test_order_side_default() { assert_eq!(OrderSide::default(), OrderSide::Buy); } // ============================================================================= // Currency Tests // ============================================================================= #[test] fn test_currency_display() { assert_eq!(format!("{}", Currency::USD), "USD"); assert_eq!(format!("{}", Currency::EUR), "EUR"); assert_eq!(format!("{}", Currency::BTC), "BTC"); } #[test] fn test_currency_default() { assert_eq!(Currency::default(), Currency::USD); } // ============================================================================= // Error Type Tests // ============================================================================= #[test] fn test_common_type_error_invalid_price() { let error = CommonTypeError::InvalidPrice { value: "abc".to_owned(), reason: "not a number".to_owned(), }; let display = format!("{}", error); assert!(display.contains("abc")); } #[test] fn test_common_type_error_invalid_quantity() { let error = CommonTypeError::InvalidQuantity { value: "xyz".to_owned(), reason: "not a number".to_owned(), }; let display = format!("{}", error); assert!(display.contains("xyz")); } #[test] fn test_common_type_error_validation() { let error = CommonTypeError::ValidationError { field: "symbol".to_owned(), reason: "cannot be empty".to_owned(), }; let display = format!("{}", error); assert!(display.contains("symbol")); } }