//! # Data Types Module //! //! Core data types and structures for market data ingestion, broker integration, //! and financial data processing in the Foxhunt HFT trading system. //! //! This module provides: //! - **Market Data Types**: Quote, Trade, Aggregate structures for market data //! - **Extended Events**: Provider-specific events from Benzinga, Databento //! - **Account & Position Types**: Trading account and position management //! - **Time Range Utilities**: Time-based data queries and filtering //! //! ## Architecture //! //! The type system is designed around the canonical event types from the `common` //! crate, with extensions for provider-specific data sources: //! //! ```text //! ┌─────────────────────┐ ┌─────────────────────┐ //! │ Common Types │────│ Extended Types │ //! │ (MarketDataEvent) │ │ (Provider Events) │ //! └─────────────────────┘ └─────────────────────┘ //! │ │ //! ▼ ▼ //! ┌─────────────────────┐ ┌─────────────────────┐ //! │ Core Events │ │ News/Sentiment │ //! │ (Quote/Trade) │ │ Analyst Ratings │ //! └─────────────────────┘ └─────────────────────┘ //! ``` //! //! ## Usage //! //! ```rust //! use data::types::{ExtendedMarketDataEvent, TimeRange, Quote, Position}; //! use common::MarketDataEvent; //! use rust_decimal::Decimal; //! //! // Create a time range for historical queries //! let time_range = TimeRange { //! start: Utc::now() - Duration::days(1), //! end: Utc::now(), //! }; //! //! // Work with extended market data events //! let extended_event = ExtendedMarketDataEvent::Core( //! MarketDataEvent::Quote(/* quote data */) //! ); //! //! // Extract symbol and timestamp //! let symbol = extended_event.symbol(); //! let timestamp = extended_event.timestamp(); //! ``` use chrono::{DateTime, Duration, Utc}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; /// Time range specification for historical data queries and filtering. /// /// Used to specify date/time boundaries for retrieving historical market data, /// backtesting periods, and data validation ranges. /// /// # Examples /// /// ```rust /// use data::types::TimeRange; /// use chrono::{Utc, Duration}; /// /// // Create a time range for the last 24 hours /// let range = TimeRange { /// start: Utc::now() - Duration::days(1), /// end: Utc::now(), /// }; /// /// // Validate the range is meaningful /// assert!(range.end > range.start); /// ``` #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct TimeRange { /// Start time (inclusive) in UTC timezone pub start: DateTime, /// End time (exclusive) in UTC timezone pub end: DateTime, } impl TimeRange { /// Create a new time range with validation. /// /// Ensures that the end time is after the start time to prevent /// invalid ranges that could cause issues in data queries. /// /// # Parameters /// /// * `start` - Start time (inclusive) /// * `end` - End time (exclusive) /// /// # Returns /// /// `Ok(TimeRange)` if valid, `Err(String)` if end <= start. /// /// # Examples /// /// ```rust /// use data::types::TimeRange; /// use chrono::{Utc, Duration}; /// /// let now = Utc::now(); /// let range = TimeRange::new( /// now - Duration::hours(1), /// now /// ).unwrap(); /// ``` pub fn new(start: DateTime, end: DateTime) -> Result { if end <= start { return Err(format!( "End time ({}) must be after start time ({})", end, start )); } Ok(Self { start, end }) } /// Create a time range for the last N hours from now. /// /// Convenience method for creating ranges relative to the current time. /// /// # Parameters /// /// * `hours` - Number of hours back from now /// /// # Examples /// /// ```rust /// use data::types::TimeRange; /// /// // Last 24 hours /// let range = TimeRange::last_hours(24); /// ``` pub fn last_hours(hours: i64) -> Self { let now = Utc::now(); Self { start: now - Duration::hours(hours), end: now, } } /// Create a time range for the last N days from now. /// /// Convenience method for creating ranges spanning multiple days. /// /// # Parameters /// /// * `days` - Number of days back from now /// /// # Examples /// /// ```rust /// use data::types::TimeRange; /// /// // Last 7 days /// let range = TimeRange::last_days(7); /// ``` pub fn last_days(days: i64) -> Self { let now = Utc::now(); Self { start: now - Duration::days(days), end: now, } } /// Create a time range for the last N minutes from now. /// /// Convenience method for creating short-term ranges. /// /// # Parameters /// /// * `minutes` - Number of minutes back from now /// /// # Examples /// /// ```rust /// use data::types::TimeRange; /// /// // Last 30 minutes /// let range = TimeRange::last_minutes(30); /// ``` pub fn last_minutes(minutes: i64) -> Self { let now = Utc::now(); Self { start: now - Duration::minutes(minutes), end: now, } } /// Check if this time range overlaps with another range. /// /// # Parameters /// /// * `other` - Another time range to check for overlap /// /// # Returns /// /// `true` if the ranges overlap, `false` otherwise /// /// # Examples /// /// ```rust /// use data::types::TimeRange; /// use chrono::{Utc, Duration}; /// /// let now = Utc::now(); /// let range1 = TimeRange::new(now - Duration::hours(2), now).unwrap(); /// let range2 = TimeRange::new(now - Duration::hours(1), now + Duration::hours(1)).unwrap(); /// assert!(range1.overlaps(&range2)); /// ``` pub fn overlaps(&self, other: &TimeRange) -> bool { self.start < other.end && other.start < self.end } /// Get the duration of this time range. /// /// # Returns /// /// The time span covered by this range. /// /// # Examples /// /// ```rust /// use data::types::TimeRange; /// use chrono::Duration; /// /// let range = TimeRange::last_hours(24); /// assert_eq!(range.duration().num_hours(), 24); /// ``` pub fn duration(&self) -> Duration { self.end - self.start } /// Check if a timestamp falls within this time range. /// /// # Parameters /// /// * `timestamp` - The timestamp to check /// /// # Returns /// /// `true` if timestamp is within [start, end), `false` otherwise. /// /// # Examples /// /// ```rust /// use data::types::TimeRange; /// use chrono::Utc; /// /// let range = TimeRange::last_hours(1); /// let now = Utc::now(); /// assert!(range.contains(now - Duration::minutes(30))); /// assert!(!range.contains(now - Duration::hours(2))); /// ``` pub fn contains(&self, timestamp: DateTime) -> bool { timestamp >= self.start && timestamp < self.end } /// Split this time range into smaller chunks of the specified duration. /// /// Useful for breaking large time ranges into manageable pieces for /// batch processing or API rate limiting. /// /// # Parameters /// /// * `chunk_duration` - Size of each chunk /// /// # Returns /// /// Vector of time ranges covering the original range. /// /// # Examples /// /// ```rust /// use data::types::TimeRange; /// use chrono::Duration; /// /// let range = TimeRange::last_hours(24); /// let chunks = range.split_into_chunks(Duration::hours(1)); /// assert_eq!(chunks.len(), 24); /// ``` pub fn split_into_chunks(&self, chunk_duration: Duration) -> Vec { let mut chunks = Vec::new(); let mut current_start = self.start; while current_start < self.end { let current_end = std::cmp::min(current_start + chunk_duration, self.end); chunks.push(TimeRange { start: current_start, end: current_end, }); current_start = current_end; } chunks } } /// Market data type classification for subscription and routing purposes. /// /// Defines the types of market data that can be requested from data providers /// and processed by the trading system. Each type corresponds to different /// levels of market information granularity. /// /// # Market Data Hierarchy /// /// - **Quotes**: Best bid/offer (Level 1) /// - **Trades**: Executed transactions /// - **Aggregates**: Time-based OHLCV bars /// - **Level2**: Full order book depth /// - **Status**: Market session information /// /// # Examples /// /// ```rust /// use data::types::MarketDataType; /// /// // Request real-time quotes for algorithmic trading /// let quote_type = MarketDataType::Quotes; /// /// // Request trade data for execution analysis /// let trade_type = MarketDataType::Trades; /// /// // Request Level 2 data for market microstructure analysis /// let level2_type = MarketDataType::Level2; /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub enum MarketDataType { /// Real-time bid/ask quotes (Level 1 market data) /// /// Contains the best bid and offer prices with sizes, updated in real-time /// as the top of the order book changes. Essential for spread analysis /// and basic trading decisions. Quotes, /// Executed trade transactions with price, size, and conditions /// /// Individual trade executions that have occurred in the market, /// including trade price, volume, exchange, and any special conditions. /// Used for volume analysis and trade pattern recognition. Trades, /// Time-aggregated OHLCV bars (Open, High, Low, Close, Volume) /// /// Summarized price action over fixed time intervals (1min, 5min, 1hour, etc.). /// Essential for technical analysis, charting, and strategy backtesting. Aggregates, /// Level 2 order book data with full market depth /// /// Complete order book showing multiple price levels on both bid and ask sides. /// Critical for microstructure analysis, liquidity assessment, and sophisticated /// execution algorithms. Level2, /// Market session status and trading halt information /// /// Information about market open/close times, trading halts, circuit breakers, /// and other market-wide events that affect trading operations. Status, } // Import MarketDataEvent from common crate - use common::MarketDataEvent directly /// Extended market data event enumeration with provider-specific events. /// /// Wraps the core `MarketDataEvent` types with additional events from specialized /// data providers like Benzinga (news, sentiment, analyst ratings) that provide /// fundamental and alternative data beyond traditional market data. /// /// This design allows the trading system to handle both: /// - **Core Market Data**: Price, volume, order book updates /// - **Alternative Data**: News sentiment, analyst ratings, unusual activity /// /// # Event Processing Pipeline /// /// ```text /// Provider Events → ExtendedMarketDataEvent → Event Router → Strategy Engine /// ↓ /// Feature Extraction /// ``` /// /// # Examples /// /// ```rust /// use data::types::ExtendedMarketDataEvent; /// use common::MarketDataEvent; /// /// // Core market data event /// let core_event = ExtendedMarketDataEvent::Core( /// MarketDataEvent::Quote(/* quote data */) /// ); /// /// // News event from Benzinga /// let news_event = ExtendedMarketDataEvent::NewsAlert( /// /* Benzinga news data */ /// ); /// /// // Extract common properties /// let symbol = core_event.symbol(); /// let timestamp = core_event.timestamp(); /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ExtendedMarketDataEvent { /// Core market data event (quotes, trades, level2, etc.) /// /// Standard market data events from the canonical event system, /// including real-time quotes, trades, aggregates, and order book updates. Core(::common::MarketDataEvent), /// News alerts and breaking news events (Benzinga) /// /// Real-time news alerts that may impact stock prices, including /// earnings announcements, FDA approvals, analyst upgrades/downgrades, /// and other market-moving news events. NewsAlert(crate::providers::common::NewsEvent), /// Market sentiment updates and analysis (Benzinga) /// /// Sentiment analysis scores derived from news articles, social media, /// and other textual data sources. Used for incorporating market mood /// into trading decisions. SentimentUpdate(crate::providers::common::SentimentEvent), /// Analyst rating changes and research reports (Benzinga) /// /// Analyst upgrades, downgrades, price target changes, and initiation /// of coverage from sell-side research firms. Critical for fundamental /// analysis and momentum strategies. AnalystRating(crate::providers::common::AnalystRatingEvent), /// Unusual options activity and flow alerts (Benzinga) /// /// Detection of unusual options trading patterns that may indicate /// informed trading or upcoming corporate events. Used for identifying /// potential price catalysts. UnusualOptions(crate::providers::common::UnusualOptionsEvent), } // Import canonical event types from common crate - use common::QuoteEvent directly // Unused imports removed - use common crate directly /// Real-time bid/ask quote data structure. /// /// Represents the best bid and offer (BBO) prices and sizes at a given moment. /// This is Level 1 market data that provides the top of the order book for /// a specific symbol. /// /// # Usage in Trading /// /// - **Spread Analysis**: Calculate bid-ask spread for liquidity assessment /// - **Market Making**: Determine optimal quote placement /// - **Execution Timing**: Assess market conditions before order placement /// - **Risk Management**: Monitor market impact and slippage potential /// /// # Examples /// /// ```rust /// use data::types::Quote; /// use rust_decimal::Decimal; /// use chrono::Utc; /// /// let quote = Quote { /// symbol: "AAPL".to_string(), /// bid: Decimal::new(15000, 2), // $150.00 /// ask: Decimal::new(15001, 2), // $150.01 /// bid_size: Decimal::new(100, 0), // 100 shares /// ask_size: Decimal::new(200, 0), // 200 shares /// exchange: Some("NASDAQ".to_string()), /// timestamp: Utc::now(), /// }; /// /// // Calculate spread /// let spread = quote.ask - quote.bid; // $0.01 /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Quote { /// Security symbol (ticker) /// /// Standard symbol identifier (e.g., "AAPL", "GOOGL", "SPY") pub symbol: String, /// Highest price buyers are willing to pay /// /// Best bid price in the order book, representing the highest price /// at which someone is willing to buy the security. pub bid: Decimal, /// Lowest price sellers are willing to accept /// /// Best ask (offer) price in the order book, representing the lowest price /// at which someone is willing to sell the security. pub ask: Decimal, /// Number of shares available at the bid price /// /// Total size/volume available at the best bid price level. pub bid_size: Decimal, /// Number of shares available at the ask price /// /// Total size/volume available at the best ask price level. pub ask_size: Decimal, /// Exchange or market center providing the quote /// /// Optional identifier for the specific exchange or market maker /// providing this quote (e.g., "NASDAQ", "NYSE", "BATS"). pub exchange: Option, /// Quote timestamp in UTC /// /// When this quote was generated or last updated by the exchange. pub timestamp: DateTime, } /// Executed trade transaction data structure. /// /// Represents an individual trade execution that occurred in the market, /// including price, volume, exchange, and any special trade conditions. /// This data is essential for: /// /// - **Volume Analysis**: Understanding trading activity patterns /// - **Price Discovery**: Observing actual transaction prices /// - **Execution Quality**: Analyzing trade execution performance /// - **Market Microstructure**: Studying trade flow and market impact /// /// # Trade Conditions /// /// The `conditions` field contains exchange-specific condition codes that /// indicate special circumstances of the trade (e.g., opening/closing auctions, /// odd lots, block trades, etc.). /// /// # Examples /// /// ```rust /// use data::types::Trade; /// use rust_decimal::Decimal; /// use chrono::Utc; /// /// let trade = Trade { /// symbol: "AAPL".to_string(), /// price: Decimal::new(15000, 2), // $150.00 /// size: Decimal::new(100, 0), // 100 shares /// exchange: Some("NASDAQ".to_string()), /// conditions: vec![], // Normal trade /// timestamp: Utc::now(), /// }; /// /// // Calculate trade value /// let value = trade.price * trade.size; // $15,000 /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "database", derive(sqlx::FromRow))] pub struct Trade { /// Security symbol (ticker) /// /// Standard symbol identifier for the traded security. pub symbol: String, /// Execution price of the trade /// /// The price at which the trade was executed, representing the /// agreed-upon value between buyer and seller. pub price: Decimal, /// Trade volume (number of shares/contracts) /// /// The quantity of securities that changed hands in this transaction. pub size: Decimal, /// Exchange or venue where the trade occurred /// /// Optional identifier for the specific exchange, ECN, or dark pool /// where this trade was executed. pub exchange: Option, /// Exchange-specific trade condition codes /// /// Array of condition codes that provide additional context about /// the trade (e.g., opening print, closing print, odd lot, block trade). /// Condition code meanings are exchange-specific. pub conditions: Vec, /// Trade execution timestamp in UTC /// /// The exact time when this trade was executed. pub timestamp: DateTime, } // Aggregate moved to common::Aggregate // Level2Update, PriceLevel, and MarketStatus moved to common::types // Subscription and DataType moved to common::types // ConnectionEvent, ConnectionStatus, and ErrorEvent moved to common::types // OrderEvent is imported from common::prelude as part of the canonical event system // See: common::events::OrderEvent // OrderStatus is imported from common::prelude as part of the canonical type system // See: common::basic::OrderStatus /// Trading position information and P&L tracking. /// /// Represents a current position in a security, including size, entry price, /// and profit/loss calculations. Essential for: /// /// - **Risk Management**: Position sizing and exposure monitoring /// - **P&L Tracking**: Real-time profit/loss calculation /// - **Portfolio Management**: Asset allocation and rebalancing /// - **Performance Analysis**: Trade outcome evaluation /// /// # Position Sizing Convention /// /// - **Positive size**: Long position (owns the security) /// - **Negative size**: Short position (borrowed and sold the security) /// - **Zero size**: No position (flat) /// /// # P&L Calculation /// /// - **Unrealized P&L**: (Current Price - Avg Entry Price) × Position Size /// - **Realized P&L**: Cumulative profit/loss from closed portions of the position /// /// # Examples /// /// ```rust /// use data::types::Position; /// use rust_decimal::Decimal; /// use chrono::Utc; /// /// let position = Position { /// symbol: "AAPL".to_string(), /// size: Decimal::new(100, 0), // Long 100 shares /// avg_price: Decimal::new(15000, 2), // Avg entry at $150.00 /// unrealized_pnl: Decimal::new(50000, 2), // $500 unrealized gain /// realized_pnl: Decimal::new(10000, 2), // $100 realized gain /// market_value: Decimal::new(1550000, 2), // $15,500 current value /// timestamp: Utc::now(), /// }; /// /// // Calculate current price /// let current_price = position.market_value / position.size; // $155.00 /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Position { /// Security symbol (ticker) /// /// Identifier for the security held in this position. pub symbol: String, /// Position size with directional sign /// /// Number of shares/contracts held: /// - Positive: Long position /// - Negative: Short position /// - Zero: Flat (no position) pub size: Decimal, /// Volume-weighted average entry price /// /// The average price paid for all shares currently held in the position, /// calculated as a volume-weighted average of all entry transactions. pub avg_price: Decimal, /// Unrealized profit/loss (mark-to-market) /// /// Current profit or loss based on the difference between the average /// entry price and the current market price, multiplied by position size. pub unrealized_pnl: Decimal, /// Realized profit/loss from closed portions /// /// Cumulative profit or loss from portions of the position that have /// been closed (sold if long, covered if short). pub realized_pnl: Decimal, /// Current market value of the position /// /// Position size multiplied by the current market price. /// For short positions, this represents the liability. pub market_value: Decimal, /// Last position update timestamp /// /// When this position information was last updated with fresh market data. pub timestamp: DateTime, } /// Trading account information and margin calculations. /// /// Comprehensive account data including equity, cash balances, buying power, /// and margin requirements. Critical for: /// /// - **Risk Management**: Ensuring sufficient capital for positions /// - **Position Sizing**: Calculating maximum allowable position sizes /// - **Margin Compliance**: Avoiding margin calls and forced liquidations /// - **Capital Allocation**: Optimizing capital usage across strategies /// /// # Margin Types /// /// - **Initial Margin**: Required capital to open a new position /// - **Maintenance Margin**: Minimum equity required to keep positions open /// - **Day Trading Buying Power**: Enhanced buying power for day trading accounts /// /// # Account Monitoring /// /// Real-time account monitoring is essential for automated trading systems /// to prevent over-leveraging and ensure regulatory compliance. /// /// # Examples /// /// ```rust /// use data::types::Account; /// use rust_decimal::Decimal; /// use chrono::Utc; /// /// let account = Account { /// account_id: "DU123456".to_string(), /// total_equity: Decimal::new(10000000, 2), // $100,000 /// available_cash: Decimal::new(5000000, 2), // $50,000 /// buying_power: Decimal::new(20000000, 2), // $200,000 (2:1 margin) /// day_trading_buying_power: Decimal::new(40000000, 2), // $400,000 (4:1 intraday) /// maintenance_margin: Decimal::new(2500000, 2), // $25,000 /// initial_margin: Decimal::new(5000000, 2), // $50,000 /// timestamp: Utc::now(), /// }; /// /// // Check if account can support a $30,000 position /// let position_value = Decimal::new(3000000, 2); // $30,000 /// let can_trade = account.buying_power >= position_value; /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Account { /// Unique account identifier /// /// Broker-specific account number or identifier used for /// routing orders and tracking positions. pub account_id: String, /// Total account equity (cash + positions) /// /// Net liquidation value of the account, including cash balance /// and the current market value of all positions. pub total_equity: Decimal, /// Available cash balance /// /// Cash available for immediate use, not tied up in positions /// or pending settlements. pub available_cash: Decimal, /// Standard buying power (typically 2:1 for stocks) /// /// Maximum dollar amount that can be used to purchase securities, /// considering margin requirements and current positions. pub buying_power: Decimal, /// Enhanced buying power for day trading (typically 4:1) /// /// Increased buying power available for intraday positions that /// will be closed before market close. Subject to PDT rules. pub day_trading_buying_power: Decimal, /// Maintenance margin requirement /// /// Minimum equity that must be maintained in the account to /// keep current positions open. Falling below triggers margin call. pub maintenance_margin: Decimal, /// Initial margin requirement /// /// Required equity to open new margined positions. /// Typically higher than maintenance margin. pub initial_margin: Decimal, /// Last account data update timestamp /// /// When this account information was last refreshed from the broker. pub timestamp: DateTime, } impl ExtendedMarketDataEvent { /// Extract the symbol identifier from any extended market data event. /// /// Returns the symbol associated with this event, regardless of whether it's /// a core market data event or a provider-specific event. For news events /// without a specific symbol, returns an empty string. /// /// # Returns /// /// A string slice containing the symbol, or empty string if not applicable. /// /// # Examples /// /// ```rust /// use data::types::ExtendedMarketDataEvent; /// use common::MarketDataEvent; /// /// let event = ExtendedMarketDataEvent::Core( /// MarketDataEvent::Quote(/* AAPL quote */) /// ); /// assert_eq!(event.symbol(), "AAPL"); /// ``` pub fn symbol(&self) -> &str { match self { ExtendedMarketDataEvent::Core(event) => event.symbol(), ExtendedMarketDataEvent::NewsAlert(n) => { // For news events, return symbol if available, otherwise empty string n.symbol.as_ref().map(|s| s.as_str()).unwrap_or("") }, ExtendedMarketDataEvent::SentimentUpdate(s) => s.symbol.as_str(), ExtendedMarketDataEvent::AnalystRating(a) => a.symbol.as_str(), ExtendedMarketDataEvent::UnusualOptions(u) => u.symbol.as_str(), } } /// Extract the timestamp from any extended market data event. /// /// Returns the timestamp when this event occurred or was generated. /// All event types should have timestamps for proper sequencing and /// time-based analysis. /// /// # Returns /// /// `Some(timestamp)` for events with timestamps, `None` if unavailable. /// /// # Examples /// /// ```rust /// use data::types::ExtendedMarketDataEvent; /// use chrono::Utc; /// /// let event = ExtendedMarketDataEvent::NewsAlert(/* news event */); /// if let Some(timestamp) = event.timestamp() { /// println!("Event occurred at: {}", timestamp); /// } /// ``` pub fn timestamp(&self) -> Option> { match self { ExtendedMarketDataEvent::Core(event) => event.timestamp(), ExtendedMarketDataEvent::NewsAlert(n) => Some(n.timestamp), ExtendedMarketDataEvent::SentimentUpdate(s) => Some(s.timestamp), ExtendedMarketDataEvent::AnalystRating(a) => Some(a.timestamp), ExtendedMarketDataEvent::UnusualOptions(u) => Some(u.timestamp), } } /// Convert an extended market data event to a core market data event. /// /// This method extracts the core `MarketDataEvent` from the extended wrapper, /// if one exists. Provider-specific events (news, sentiment, etc.) cannot be /// converted to core events since they represent different data types. /// /// # Returns /// /// - `Some(MarketDataEvent)` for Core events /// - `None` for provider-specific events that have no core equivalent /// /// # Use Cases /// /// - Filtering out alternative data to focus on price/volume data /// - Converting to systems that only handle core market data /// - Separating traditional and alternative data streams /// /// # Examples /// /// ```rust /// use data::types::ExtendedMarketDataEvent; /// /// let extended_event = ExtendedMarketDataEvent::Core(/* market data */); /// if let Some(core_event) = extended_event.into_core_event() { /// // Process as standard market data /// } /// /// let news_event = ExtendedMarketDataEvent::NewsAlert(/* news */); /// assert!(news_event.into_core_event().is_none()); /// ``` pub fn into_core_event(self) -> Option { match self { ExtendedMarketDataEvent::Core(event) => Some(event), _ => None, // Provider-specific events don't have core equivalents } } } /// Extract core market data events from a collection of extended events. /// /// Filters a vector of `ExtendedMarketDataEvent` to extract only the core /// `MarketDataEvent` instances, discarding provider-specific events like /// news alerts and sentiment updates. /// /// This is useful when you need to process only traditional market data /// (quotes, trades, level2) and ignore alternative data sources. /// /// # Parameters /// /// * `extended_events` - Collection of extended market data events to filter /// /// # Returns /// /// Vector containing only the core market data events, preserving order. /// /// # Examples /// /// ```rust /// use data::types::{ExtendedMarketDataEvent, extract_core_events}; /// use common::MarketDataEvent; /// /// let extended_events = vec![ /// ExtendedMarketDataEvent::Core(/* quote event */), /// ExtendedMarketDataEvent::NewsAlert(/* news event */), /// ExtendedMarketDataEvent::Core(/* trade event */), /// ]; /// /// let core_events = extract_core_events(extended_events); /// // Result contains only the quote and trade events /// assert_eq!(core_events.len(), 2); /// ``` /// /// # Performance /// /// This function uses iterator combinators for efficient filtering without /// intermediate allocations beyond the final result vector. pub fn extract_core_events( extended_events: Vec, ) -> Vec { extended_events .into_iter() .filter_map(|event| event.into_core_event()) .collect() } /// Extract timestamp from any core market data event. /// /// Utility function to extract the timestamp from a `MarketDataEvent` since /// we cannot add methods to the enum defined in the common crate. Different /// event types store timestamps in different fields, so this function handles /// the pattern matching. /// /// # Parameters /// /// * `event` - Reference to a core market data event /// /// # Returns /// /// `Some(timestamp)` for all event types, or `None` if timestamp is missing /// (though all current event types include timestamps). /// /// # Event Timestamp Fields /// /// - **Quote/Trade/Level2**: Direct `timestamp` field /// - **Aggregate/Bar**: Uses `end_timestamp` (when the period ended) /// - **Status/Connection/Error**: Direct `timestamp` field /// - **OrderBook events**: Direct `timestamp` field /// /// # Examples /// /// ```rust /// use data::types::get_event_timestamp; /// use common::MarketDataEvent; /// /// let event = MarketDataEvent::Quote(/* quote data */); /// if let Some(timestamp) = get_event_timestamp(&event) { /// println!("Event timestamp: {}", timestamp); /// } /// ``` /// /// # Use Cases /// /// - Event sequencing and ordering /// - Latency analysis and performance monitoring /// - Time-based filtering and windowing /// - Synchronization across data sources pub fn get_event_timestamp(event: &common::MarketDataEvent) -> Option> { match event { common::MarketDataEvent::Quote(q) => Some(q.timestamp), common::MarketDataEvent::Trade(t) => Some(t.timestamp), common::MarketDataEvent::Aggregate(a) => Some(a.end_timestamp), common::MarketDataEvent::Bar(b) => Some(b.end_timestamp), common::MarketDataEvent::Level2(l) => Some(l.timestamp), common::MarketDataEvent::Status(s) => Some(s.timestamp), common::MarketDataEvent::ConnectionStatus(c) => Some(c.timestamp), common::MarketDataEvent::Error(e) => Some(e.timestamp), common::MarketDataEvent::OrderBook(o) => Some(o.timestamp), common::MarketDataEvent::OrderBookL2Snapshot(s) => Some(s.timestamp), common::MarketDataEvent::OrderBookL2Update(u) => Some(u.timestamp), } } // Note: Subscription implementation moved to common crate // Use common::Subscription methods #[cfg(test)] mod tests { use super::*; use chrono::{Duration, Utc}; #[test] fn test_time_range_creation() { let now = Utc::now(); let start = now - Duration::hours(1); let end = now; let range = TimeRange::new(start, end).unwrap(); assert_eq!(range.start, start); assert_eq!(range.end, end); } #[test] fn test_time_range_validation() { let now = Utc::now(); let result = TimeRange::new(now, now - Duration::hours(1)); assert!(result.is_err()); } #[test] fn test_time_range_contains() { let range = TimeRange::last_hours(2); let now = Utc::now(); assert!(range.contains(now - Duration::minutes(30))); assert!(!range.contains(now - Duration::hours(3))); assert!(!range.contains(now + Duration::minutes(30))); } #[test] fn test_time_range_split() { let range = TimeRange::last_hours(4); let chunks = range.split_into_chunks(Duration::hours(1)); assert_eq!(chunks.len(), 4); for chunk in &chunks { assert_eq!(chunk.duration(), Duration::hours(1)); } } #[test] fn test_subscription_creation() { let sub = common::Subscription { symbols: vec!["AAPL".to_string(), "GOOGL".to_string()], data_types: vec![common::DataType::Quotes], exchanges: vec![], }; assert_eq!(sub.symbols.len(), 2); assert_eq!(sub.data_types.len(), 1); assert!(matches!(sub.data_types[0], common::DataType::Quotes)); } #[test] fn test_market_data_event_symbol() { let quote = common::MarketDataEvent::Quote(common::QuoteEvent { symbol: "AAPL".to_string(), bid: Some(Decimal::new(15000, 2)), // 150.00 ask: Some(Decimal::new(15001, 2)), // 150.01 bid_size: Some(Decimal::new(100, 0)), ask_size: Some(Decimal::new(200, 0)), exchange: Some("NASDAQ".to_string()), bid_exchange: Some("NASDAQ".to_string()), ask_exchange: Some("NASDAQ".to_string()), conditions: vec![], timestamp: Utc::now(), sequence: 0, }); assert_eq!(quote.symbol(), "AAPL"); } #[test] fn test_extended_event_symbol_extraction() { // Test would need actual event instances to work properly // Placeholder test for extended event functionality } #[test] fn test_extract_core_events() { // Test would need actual event instances to work properly // Placeholder test for core event extraction } #[test] fn test_time_range_duration() { let start = Utc::now(); let end = start + Duration::hours(2); let range = TimeRange::new(start, end).unwrap(); assert_eq!(range.duration(), Duration::hours(2)); } #[test] fn test_time_range_last_days() { let range = TimeRange::last_days(7); let duration = range.duration(); // Should be approximately 7 days (allow small tolerance) assert!((duration - Duration::days(7)).num_seconds().abs() < 10); } #[test] fn test_time_range_last_minutes() { let range = TimeRange::last_minutes(30); let duration = range.duration(); // Should be approximately 30 minutes assert!((duration - Duration::minutes(30)).num_seconds().abs() < 10); } #[test] fn test_time_range_overlaps() { let range1 = TimeRange { start: Utc::now() - Duration::hours(2), end: Utc::now(), }; let range2 = TimeRange { start: Utc::now() - Duration::hours(1), end: Utc::now() + Duration::hours(1), }; assert!(range1.overlaps(&range2)); assert!(range2.overlaps(&range1)); } #[test] fn test_time_range_no_overlap() { let range1 = TimeRange { start: Utc::now() - Duration::hours(3), end: Utc::now() - Duration::hours(2), }; let range2 = TimeRange { start: Utc::now() - Duration::hours(1), end: Utc::now(), }; assert!(!range1.overlaps(&range2)); assert!(!range2.overlaps(&range1)); } #[test] fn test_time_range_split_exact() { let range = TimeRange::last_hours(6); let chunks = range.split_into_chunks(Duration::hours(2)); assert_eq!(chunks.len(), 3); // Verify each chunk is 2 hours for chunk in &chunks { assert_eq!(chunk.duration(), Duration::hours(2)); } // Verify chunks are contiguous for i in 0..chunks.len() - 1 { assert_eq!(chunks[i].end, chunks[i + 1].start); } } #[test] fn test_time_range_split_uneven() { let range = TimeRange::last_hours(5); let chunks = range.split_into_chunks(Duration::hours(2)); // Should have 3 chunks (2h + 2h + 1h) assert_eq!(chunks.len(), 3); // First two should be 2 hours assert_eq!(chunks[0].duration(), Duration::hours(2)); assert_eq!(chunks[1].duration(), Duration::hours(2)); // Last chunk should be approximately 1 hour (with small tolerance) let last_duration = chunks[2].duration(); assert!((last_duration - Duration::hours(1)).num_seconds().abs() < 10); } #[test] fn test_get_event_timestamp_quote() { let timestamp = Utc::now(); let quote = common::MarketDataEvent::Quote(common::QuoteEvent { symbol: "BTC".to_string(), bid: Some(Decimal::new(50000, 0)), ask: Some(Decimal::new(50001, 0)), bid_size: Some(Decimal::ONE), ask_size: Some(Decimal::ONE), exchange: Some("COINBASE".to_string()), bid_exchange: Some("COINBASE".to_string()), ask_exchange: Some("COINBASE".to_string()), conditions: vec![], timestamp, sequence: 1, }); assert_eq!(get_event_timestamp("e), Some(timestamp)); } #[test] fn test_get_event_timestamp_trade() { let timestamp = Utc::now(); let trade = common::MarketDataEvent::Trade(common::TradeEvent { symbol: "ETH".to_string(), price: Decimal::new(3000, 0), size: Decimal::new(10, 0), exchange: Some("BINANCE".to_string()), conditions: vec![], timestamp, trade_id: Some("trade123".to_string()), sequence: 1, }); assert_eq!(get_event_timestamp(&trade), Some(timestamp)); } #[test] fn test_get_event_timestamp_aggregate() { let end_timestamp = Utc::now(); let agg = common::MarketDataEvent::Aggregate(common::Aggregate { symbol: "AAPL".to_string(), open: Decimal::new(150, 0), high: Decimal::new(152, 0), low: Decimal::new(149, 0), close: Decimal::new(151, 0), volume: Decimal::new(10000, 0), vwap: Some(Decimal::new(15050, 2)), start_timestamp: end_timestamp - Duration::minutes(1), end_timestamp, }); assert_eq!(get_event_timestamp(&agg), Some(end_timestamp)); } #[test] fn test_subscription_multiple_symbols() { let sub = common::Subscription { symbols: vec!["BTC".to_string(), "ETH".to_string(), "SOL".to_string()], data_types: vec![common::DataType::Quotes, common::DataType::Trades], exchanges: vec!["COINBASE".to_string()], }; assert_eq!(sub.symbols.len(), 3); assert_eq!(sub.data_types.len(), 2); assert_eq!(sub.exchanges.len(), 1); } #[test] fn test_time_range_edge_cases() { let now = Utc::now(); // Same timestamp should fail let result = TimeRange::new(now, now); assert!(result.is_err()); // Very small range should succeed let result = TimeRange::new(now, now + Duration::milliseconds(1)); assert!(result.is_ok()); } }