Files
foxhunt/data/src/types.rs
jgrusewski 6bd5b18465 🔧 Wave 33: Test Compilation Improvements - 57 errors remaining
**Progress: 1,178 → 57 test errors (95% reduction)**

## Status Summary
-  Production code: Compiles cleanly (0 errors)
- ⚠️  Test code: 57 errors remain (massive improvement)
- ⚙️  All services build successfully
- 📊 Warning count: 253 (target: <20) - AGENTS WILL FIX

## Remaining Test Errors (57 total)
### Primary Issues:
1. 23× E0308 mismatched types
2. 17× E0433 undeclared Decimal
3. 15× E0433 compliance module not found
4. 6× E0624 private method access
5. Various import and type issues

## Next Phase: Wave 33-2
Launch 10+ parallel agents to:
- Fix remaining 57 test compilation errors
- Reduce 253 warnings to <20
- Achieve 95% test coverage
- Ensure all tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-01 21:24:28 +02:00

1258 lines
41 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! # 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<Utc>,
/// End time (exclusive) in UTC timezone
pub end: DateTime<Utc>,
}
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<Utc>, end: DateTime<Utc>) -> Result<Self, String> {
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<Utc>) -> 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<TimeRange> {
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<String>,
/// Quote timestamp in UTC
///
/// When this quote was generated or last updated by the exchange.
pub timestamp: DateTime<Utc>,
}
/// 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<String>,
/// 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<u32>,
/// Trade execution timestamp in UTC
///
/// The exact time when this trade was executed.
pub timestamp: DateTime<Utc>,
}
// 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<Utc>,
}
/// 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<Utc>,
}
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<DateTime<Utc>> {
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<common::MarketDataEvent> {
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<ExtendedMarketDataEvent>,
) -> Vec<common::MarketDataEvent> {
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<DateTime<Utc>> {
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(&quote), 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());
}
}