refactor: split common/types.rs (4909 LOC) into 10 focused modules

Split god file into: aliases.rs, domain.rs, events.rs, identifiers.rs,
market_data.rs, market.rs, service.rs, trading_enums.rs, type_error.rs.
Re-exports via mod.rs maintain backward compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-01 20:32:52 +01:00
parent 7a602274cd
commit c2118b3073
11 changed files with 5007 additions and 4909 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
//! Type aliases for complex shared types
//!
//! Common type aliases used throughout the Foxhunt HFT trading system.
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
// =============================================================================
// Type Aliases for Complex Types
// =============================================================================
/// Common error type for async operations
pub type AsyncResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
/// Thread-safe hash map for shared state
pub type SharedHashMap<K, V> = Arc<RwLock<HashMap<K, V>>>;
/// Thread-safe hash map with Mutex for shared state
pub type MutexHashMap<K, V> = Arc<Mutex<HashMap<K, V>>>;
/// Thread-safe container for any value
pub type SharedValue<T> = Arc<RwLock<T>>;
/// Thread-safe container with Mutex for any value
pub type MutexValue<T> = Arc<Mutex<T>>;
// Trading-specific type aliases
/// Map of positions by symbol
pub type PositionMap<T> = SharedHashMap<String, T>;
/// Map of orders by order ID
pub type OrderMap<T> = SharedHashMap<String, T>;
/// Map of accounts by account ID
pub type AccountMap<T> = SharedHashMap<String, T>;
/// Map of instruments by instrument ID
pub type InstrumentMap<T> = SharedHashMap<String, T>;
/// Map of market data by symbol
pub type MarketDataMap<T> = SharedHashMap<String, T>;
/// Cache entry with timestamp
pub type CacheEntry<T> = (T, DateTime<Utc>);
/// Cache map with timestamped entries
pub type CacheMap<K, V> = SharedHashMap<K, CacheEntry<V>>;
/// Risk factor loadings by instrument
pub type RiskFactorMap = SharedHashMap<String, HashMap<String, Decimal>>;
/// Performance metrics history
pub type PerformanceHistory<T> = SharedHashMap<String, std::collections::VecDeque<T>>;
/// Model registry for ML models
pub type ModelRegistry<T> = SharedHashMap<String, T>;
/// Generic configuration cache
pub type ConfigCache<K, V> = SharedHashMap<K, V>;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
//! Order event types
//!
//! Event types for the complete order lifecycle.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::{OrderId, OrderSide, OrderType, Price, Quantity, Symbol};
// =============================================================================
// 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<Price>,
/// Timestamp when the event occurred
pub timestamp: DateTime<Utc>,
/// 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<Quantity>,
/// Previous price for modifications
pub previous_price: Option<Price>,
/// Reason for cancellation or modification
pub reason: Option<String>,
}
/// 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,
}

View File

@@ -0,0 +1,248 @@
//! Identifier types and decimal extensions
//!
//! Newtype identifiers (EventId, FillId, AggregateId, AssetId, ClientId)
//! and the DecimalExt trait.
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::fmt;
use super::CommonTypeError;
// =============================================================================
// 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<S: Into<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<String> 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<Self>
where
Self: Sized;
/// Calculate square root of Decimal
fn sqrt(&self) -> Option<Self>
where
Self: Sized;
}
impl DecimalExt for Decimal {
fn from_f64(value: f64) -> Option<Self> {
Decimal::from_f64_retain(value)
}
fn sqrt(&self) -> Option<Self> {
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<S: Into<String>>(id: S) -> Result<Self, CommonTypeError> {
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<S: Into<String>>(id: S) -> Result<Self, CommonTypeError> {
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<S: Into<String>>(id: S) -> Result<Self, CommonTypeError> {
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<S: Into<String>>(id: S) -> Result<Self, CommonTypeError> {
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)
}
}

View File

@@ -0,0 +1,472 @@
//! Market types for trading venues and signals
//!
//! Contains MarketRegime, TickType, Exchange, MarketTick, TradingSignal, and OrderRef.
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
use uuid::Uuid;
use crate::error::CommonError;
use super::{
CommonTypeError, HftTimestamp, Order, OrderId, OrderSide, OrderType, Price, Quantity, Symbol,
};
// =============================================================================
// 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 Borse
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<Self, Self::Err> {
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<Self, CommonError> {
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<String, String>,
}
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<Self, CommonTypeError> {
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<Price> {
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,
}
}
}

View File

@@ -0,0 +1,562 @@
//! Market data event types
//!
//! Consolidated market data types from data and trading_engine crates.
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::fmt;
use crate::error::ErrorCategory;
use super::{Price, Quantity};
// =============================================================================
// 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<Decimal>,
/// Ask price
pub ask: Option<Decimal>,
/// Bid size
pub bid_size: Option<Decimal>,
/// Ask size
pub ask_size: Option<Decimal>,
/// Exchange
pub exchange: Option<String>,
/// Bid exchange
pub bid_exchange: Option<String>,
/// Ask exchange
pub ask_exchange: Option<String>,
/// Quote conditions
pub conditions: Vec<String>,
/// Timestamp
pub timestamp: DateTime<Utc>,
/// Sequence number
pub sequence: u64,
}
impl QuoteEvent {
/// Create a new quote event
#[must_use]
pub const fn new(symbol: String, timestamp: DateTime<Utc>) -> 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<S: Into<String>>(mut self, exchange: S) -> Self {
self.exchange = Some(exchange.into());
self
}
/// Set bid exchange
pub fn with_bid_exchange<S: Into<String>>(mut self, exchange: S) -> Self {
self.bid_exchange = Some(exchange.into());
self
}
/// Set ask exchange
pub fn with_ask_exchange<S: Into<String>>(mut self, exchange: S) -> Self {
self.ask_exchange = Some(exchange.into());
self
}
/// Add quote condition
pub fn with_condition<S: Into<String>>(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<Decimal> {
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<Decimal> {
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<String>,
/// Exchange
pub exchange: Option<String>,
/// Trade conditions
pub conditions: Vec<String>,
/// Timestamp
pub timestamp: DateTime<Utc>,
/// 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<Utc>,
) -> Self {
Self {
symbol,
price,
size,
trade_id: None,
exchange: None,
conditions: Vec::new(),
timestamp,
sequence: 0,
}
}
/// Set trade ID
pub fn with_trade_id<S: Into<String>>(mut self, trade_id: S) -> Self {
self.trade_id = Some(trade_id.into());
self
}
/// Set exchange
pub fn with_exchange<S: Into<String>>(mut self, exchange: S) -> Self {
self.exchange = Some(exchange.into());
self
}
/// Add trade condition
pub fn with_condition<S: Into<String>>(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<Decimal>,
/// Start timestamp
pub start_timestamp: DateTime<Utc>,
/// End timestamp
pub end_timestamp: DateTime<Utc>,
}
/// 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<Decimal>,
/// Start timestamp
pub start_timestamp: DateTime<Utc>,
/// End timestamp
pub end_timestamp: DateTime<Utc>,
/// 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<PriceLevel>,
/// Ask levels
pub asks: Vec<PriceLevel>,
/// Timestamp
pub timestamp: DateTime<Utc>,
}
/// 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<PriceLevel>,
/// Ask levels (price, size) sorted by price ascending
pub asks: Vec<PriceLevel>,
/// Exchange
pub exchange: String,
/// Timestamp of snapshot
pub timestamp: DateTime<Utc>,
/// 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<PriceLevelChange>,
/// Changes to ask levels
pub ask_changes: Vec<PriceLevelChange>,
/// Exchange
pub exchange: String,
/// Timestamp of update
pub timestamp: DateTime<Utc>,
/// 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<Utc>,
}
/// 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<String>,
/// Timestamp
pub timestamp: DateTime<Utc>,
}
/// 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<Utc>,
}
// 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<Utc>,
/// 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<String>,
/// Data types to subscribe to
pub data_types: Vec<DataType>,
/// Exchange filter (optional)
pub exchanges: Vec<String>,
}
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<DateTime<Utc>> {
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),
}
}
}
/// 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<S: Into<String>>(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)
}
}
impl fmt::Display for ConnectionInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.url())
}
}
/// Resource limits for services
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ResourceLimits {
/// Maximum memory usage in bytes
pub max_memory_bytes: Option<u64>,
/// Maximum CPU usage as percentage (0-100)
pub max_cpu_percent: Option<f64>,
/// Maximum number of open file descriptors
pub max_file_descriptors: Option<u32>,
/// Maximum number of network connections
pub max_connections: Option<u32>,
}

View File

@@ -0,0 +1,507 @@
//! 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.
mod aliases;
mod domain;
mod events;
mod identifiers;
mod market;
mod market_data;
mod service;
mod trading_enums;
mod type_error;
pub use aliases::*;
pub use domain::*;
pub use events::*;
pub use identifiers::*;
pub use market::*;
pub use market_data::*;
pub use service::*;
pub use trading_enums::*;
pub use type_error::*;
// =============================================================================
// 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 = rust_decimal::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 = rust_decimal::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"));
}
}

View File

@@ -0,0 +1,165 @@
//! Service infrastructure types
//!
//! Types for service identification, status, and configuration versioning.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;
// =============================================================================
// 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<S: Into<String>>(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<String> 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<Utc>,
/// Optional description of changes
pub description: Option<String>,
}
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<S: Into<String>>(version: u64, description: S) -> Self {
Self {
version,
timestamp: Utc::now(),
description: Some(description.into()),
}
}
}
// TECHNICAL DEBT ELIMINATED - Use DateTime<Utc> directly instead of Timestamp alias
/// Timestamp type alias for consistency across the system
pub type Timestamp = DateTime<Utc>;
/// 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
}
}
impl fmt::Display for RequestId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

View File

@@ -0,0 +1,295 @@
//! Trading enumeration types
//!
//! Canonical definitions for order types, broker types, order status,
//! order side, currency, and time-in-force.
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt;
// =============================================================================
// 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<i32> for OrderType {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
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<i32> for OrderStatus {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
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<i32> for OrderSide {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
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
}
}

View File

@@ -0,0 +1,283 @@
//! Common error types for trading operations
//!
//! Error types that implement Send + Sync for use in async contexts.
use serde::{Deserialize, Serialize};
// =============================================================================
// 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<V>(self, mut map: V) -> Result<CommonTypeError, V::Error>
where
V: MapAccess<'de>,
{
// For simplicity, deserialize everything as ConversionError
let mut message = String::new();
while let Some(key) = map.next_key::<String>()? {
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,
)
}
}