//! Comprehensive tests for market_data module //! //! Tests cover: //! - MarketDataEvent creation and variants //! - Trade, Quote, Bar, OrderBook, and News events //! - Timestamp extraction from events //! - Serialization/deserialization //! - BarInterval variants use chrono::Utc; use common::market_data::{ BarEvent, BarInterval, MarketDataEvent, NewsEvent, OrderBookEvent, QuoteEvent, TradeEvent, }; use common::types::{OrderSide, Price, Quantity, Symbol}; // ============================================================================ // TradeEvent Tests // ============================================================================ #[test] fn test_trade_event_creation() { let symbol = Symbol::new("AAPL".to_string()); let price = Price::from_f64(150.50).unwrap(); let quantity = Quantity::from_f64(100.0).unwrap(); let timestamp = Utc::now(); let trade = TradeEvent { symbol: symbol.clone(), price, quantity, side: OrderSide::Buy, timestamp, trade_id: "TRADE-001".to_string(), }; assert_eq!(trade.symbol, symbol); assert_eq!(trade.price, price); assert_eq!(trade.quantity, quantity); assert_eq!(trade.side, OrderSide::Buy); assert_eq!(trade.timestamp, timestamp); assert_eq!(trade.trade_id, "TRADE-001"); } #[test] fn test_trade_event_serialization() { let symbol = Symbol::new("AAPL".to_string()); let price = Price::from_f64(150.50).unwrap(); let quantity = Quantity::from_f64(100.0).unwrap(); let timestamp = Utc::now(); let trade = TradeEvent { symbol: symbol.clone(), price, quantity, side: OrderSide::Sell, timestamp, trade_id: "TRADE-002".to_string(), }; let json = serde_json::to_string(&trade).expect("Failed to serialize"); let deserialized: TradeEvent = serde_json::from_str(&json).expect("Failed to deserialize"); assert_eq!(deserialized.symbol, symbol); assert_eq!(deserialized.price, price); assert_eq!(deserialized.side, OrderSide::Sell); } // ============================================================================ // QuoteEvent Tests // ============================================================================ #[test] fn test_quote_event_creation() { let symbol = Symbol::new("GOOGL".to_string()); let bid_price = Price::from_f64(2800.00).unwrap(); let bid_quantity = Quantity::from_f64(50.0).unwrap(); let ask_price = Price::from_f64(2800.50).unwrap(); let ask_quantity = Quantity::from_f64(75.0).unwrap(); let timestamp = Utc::now(); let quote = QuoteEvent { symbol: symbol.clone(), bid_price, bid_quantity, ask_price, ask_quantity, timestamp, }; assert_eq!(quote.symbol, symbol); assert_eq!(quote.bid_price, bid_price); assert_eq!(quote.bid_quantity, bid_quantity); assert_eq!(quote.ask_price, ask_price); assert_eq!(quote.ask_quantity, ask_quantity); } #[test] fn test_quote_event_spread() { let symbol = Symbol::new("MSFT".to_string()); let bid_price = Price::from_f64(300.00).unwrap(); let bid_quantity = Quantity::from_f64(100.0).unwrap(); let ask_price = Price::from_f64(300.10).unwrap(); let ask_quantity = Quantity::from_f64(100.0).unwrap(); let timestamp = Utc::now(); let quote = QuoteEvent { symbol, bid_price, bid_quantity, ask_price, ask_quantity, timestamp, }; // Spread should be 0.10 let spread = quote.ask_price - quote.bid_price; assert_eq!(spread, Price::from_f64(0.10).unwrap()); } #[test] fn test_quote_event_serialization() { let symbol = Symbol::new("TSLA".to_string()); let quote = QuoteEvent { symbol: symbol.clone(), bid_price: Price::from_f64(200.00).unwrap(), bid_quantity: Quantity::from_f64(10.0).unwrap(), ask_price: Price::from_f64(200.05).unwrap(), ask_quantity: Quantity::from_f64(15.0).unwrap(), timestamp: Utc::now(), }; let json = serde_json::to_string("e).expect("Failed to serialize"); let deserialized: QuoteEvent = serde_json::from_str(&json).expect("Failed to deserialize"); assert_eq!(deserialized.symbol, symbol); assert_eq!(deserialized.bid_price, quote.bid_price); assert_eq!(deserialized.ask_price, quote.ask_price); } // ============================================================================ // BarEvent Tests // ============================================================================ #[test] fn test_bar_event_creation() { let symbol = Symbol::new("SPY".to_string()); let open = Price::from_f64(450.00).unwrap(); let high = Price::from_f64(451.50).unwrap(); let low = Price::from_f64(449.50).unwrap(); let close = Price::from_f64(450.75).unwrap(); let volume = Quantity::from_f64(1000000.0).unwrap(); let timestamp = Utc::now(); let bar = BarEvent { symbol: symbol.clone(), open, high, low, close, volume, timestamp, interval: BarInterval::Minute1, }; assert_eq!(bar.symbol, symbol); assert_eq!(bar.open, open); assert_eq!(bar.high, high); assert_eq!(bar.low, low); assert_eq!(bar.close, close); assert_eq!(bar.volume, volume); } #[test] fn test_bar_interval_variants() { let intervals = vec![ BarInterval::Second1, BarInterval::Minute1, BarInterval::Minute5, BarInterval::Minute15, BarInterval::Hour1, BarInterval::Day1, ]; for interval in intervals { // Test Debug formatting let debug_str = format!("{:?}", interval); assert!(!debug_str.is_empty()); // Test serialization let json = serde_json::to_string(&interval).expect("Failed to serialize"); let deserialized: BarInterval = serde_json::from_str(&json).expect("Failed to deserialize"); // BarInterval is Copy, so we can compare directly assert_eq!(format!("{:?}", deserialized), format!("{:?}", interval)); } } #[test] fn test_bar_event_serialization() { let bar = BarEvent { symbol: Symbol::new("QQQ".to_string()), open: Price::from_f64(350.00).unwrap(), high: Price::from_f64(351.00).unwrap(), low: Price::from_f64(349.50).unwrap(), close: Price::from_f64(350.50).unwrap(), volume: Quantity::from_f64(500000.0).unwrap(), timestamp: Utc::now(), interval: BarInterval::Minute5, }; let json = serde_json::to_string(&bar).expect("Failed to serialize"); let deserialized: BarEvent = serde_json::from_str(&json).expect("Failed to deserialize"); assert_eq!(deserialized.symbol, bar.symbol); assert_eq!(deserialized.open, bar.open); assert_eq!(deserialized.high, bar.high); assert_eq!(deserialized.low, bar.low); assert_eq!(deserialized.close, bar.close); } // ============================================================================ // OrderBookEvent Tests // ============================================================================ #[test] fn test_order_book_event_creation() { let symbol = Symbol::new("BTC-USD".to_string()); let bids = vec![ ( Price::from_f64(50000.00).unwrap(), Quantity::from_f64(0.5).unwrap(), ), ( Price::from_f64(49999.00).unwrap(), Quantity::from_f64(1.0).unwrap(), ), ( Price::from_f64(49998.00).unwrap(), Quantity::from_f64(2.0).unwrap(), ), ]; let asks = vec![ ( Price::from_f64(50001.00).unwrap(), Quantity::from_f64(0.5).unwrap(), ), ( Price::from_f64(50002.00).unwrap(), Quantity::from_f64(1.0).unwrap(), ), ( Price::from_f64(50003.00).unwrap(), Quantity::from_f64(2.0).unwrap(), ), ]; let timestamp = Utc::now(); let order_book = OrderBookEvent { symbol: symbol.clone(), bids: bids.clone(), asks: asks.clone(), timestamp, }; assert_eq!(order_book.symbol, symbol); assert_eq!(order_book.bids.len(), 3); assert_eq!(order_book.asks.len(), 3); assert_eq!(order_book.bids[0].0, Price::from_f64(50000.00).unwrap()); } #[test] fn test_order_book_event_empty_levels() { let symbol = Symbol::new("ETH-USD".to_string()); let order_book = OrderBookEvent { symbol: symbol.clone(), bids: vec![], asks: vec![], timestamp: Utc::now(), }; assert_eq!(order_book.symbol, symbol); assert!(order_book.bids.is_empty()); assert!(order_book.asks.is_empty()); } #[test] fn test_order_book_event_serialization() { let order_book = OrderBookEvent { symbol: Symbol::new("EUR-USD".to_string()), bids: vec![( Price::from_f64(1.1000).unwrap(), Quantity::from_f64(100000.0).unwrap(), )], asks: vec![( Price::from_f64(1.1001).unwrap(), Quantity::from_f64(100000.0).unwrap(), )], timestamp: Utc::now(), }; let json = serde_json::to_string(&order_book).expect("Failed to serialize"); let deserialized: OrderBookEvent = serde_json::from_str(&json).expect("Failed to deserialize"); assert_eq!(deserialized.symbol, order_book.symbol); assert_eq!(deserialized.bids.len(), 1); assert_eq!(deserialized.asks.len(), 1); } // ============================================================================ // NewsEvent Tests // ============================================================================ #[test] fn test_news_event_creation() { let symbol = Symbol::new("AAPL".to_string()); let timestamp = Utc::now(); let news = NewsEvent { symbol: Some(symbol.clone()), headline: "Apple announces new product".to_string(), content: "Apple Inc. announced today...".to_string(), timestamp, source: "Bloomberg".to_string(), }; assert_eq!(news.symbol, Some(symbol)); assert_eq!(news.headline, "Apple announces new product"); assert!(news.content.contains("Apple Inc.")); assert_eq!(news.source, "Bloomberg"); } #[test] fn test_news_event_no_symbol() { let news = NewsEvent { symbol: None, headline: "Market-wide news".to_string(), content: "General market commentary".to_string(), timestamp: Utc::now(), source: "Reuters".to_string(), }; assert!(news.symbol.is_none()); assert_eq!(news.headline, "Market-wide news"); } #[test] fn test_news_event_serialization() { let news = NewsEvent { symbol: Some(Symbol::new("TSLA".to_string())), headline: "Electric vehicle sales surge".to_string(), content: "Tesla reports record deliveries".to_string(), timestamp: Utc::now(), source: "CNBC".to_string(), }; let json = serde_json::to_string(&news).expect("Failed to serialize"); let deserialized: NewsEvent = serde_json::from_str(&json).expect("Failed to deserialize"); assert_eq!(deserialized.symbol, news.symbol); assert_eq!(deserialized.headline, news.headline); assert_eq!(deserialized.source, news.source); } // ============================================================================ // MarketDataEvent Tests // ============================================================================ #[test] fn test_market_data_event_trade_variant() { let trade = TradeEvent { symbol: Symbol::new("AAPL".to_string()), price: Price::from_f64(150.00).unwrap(), quantity: Quantity::from_f64(100.0).unwrap(), side: OrderSide::Buy, timestamp: Utc::now(), trade_id: "T1".to_string(), }; let event = MarketDataEvent::Trade(trade.clone()); if let MarketDataEvent::Trade(t) = event { assert_eq!(t.symbol, trade.symbol); assert_eq!(t.price, trade.price); } else { panic!("Expected Trade variant"); } } #[test] fn test_market_data_event_quote_variant() { let quote = QuoteEvent { symbol: Symbol::new("GOOGL".to_string()), bid_price: Price::from_f64(2800.00).unwrap(), bid_quantity: Quantity::from_f64(50.0).unwrap(), ask_price: Price::from_f64(2800.50).unwrap(), ask_quantity: Quantity::from_f64(75.0).unwrap(), timestamp: Utc::now(), }; let event = MarketDataEvent::Quote(quote.clone()); if let MarketDataEvent::Quote(q) = event { assert_eq!(q.symbol, quote.symbol); assert_eq!(q.bid_price, quote.bid_price); } else { panic!("Expected Quote variant"); } } #[test] fn test_market_data_event_bar_variant() { let bar = BarEvent { symbol: Symbol::new("SPY".to_string()), open: Price::from_f64(450.00).unwrap(), high: Price::from_f64(451.00).unwrap(), low: Price::from_f64(449.00).unwrap(), close: Price::from_f64(450.50).unwrap(), volume: Quantity::from_f64(1000000.0).unwrap(), timestamp: Utc::now(), interval: BarInterval::Minute1, }; let event = MarketDataEvent::Bar(bar.clone()); if let MarketDataEvent::Bar(b) = event { assert_eq!(b.symbol, bar.symbol); assert_eq!(b.open, bar.open); } else { panic!("Expected Bar variant"); } } #[test] fn test_market_data_event_order_book_variant() { let order_book = OrderBookEvent { symbol: Symbol::new("BTC-USD".to_string()), bids: vec![( Price::from_f64(50000.00).unwrap(), Quantity::from_f64(1.0).unwrap(), )], asks: vec![( Price::from_f64(50001.00).unwrap(), Quantity::from_f64(1.0).unwrap(), )], timestamp: Utc::now(), }; let event = MarketDataEvent::OrderBook(order_book.clone()); if let MarketDataEvent::OrderBook(ob) = event { assert_eq!(ob.symbol, order_book.symbol); assert_eq!(ob.bids.len(), 1); } else { panic!("Expected OrderBook variant"); } } #[test] fn test_market_data_event_news_variant() { let news = NewsEvent { symbol: Some(Symbol::new("AAPL".to_string())), headline: "Breaking news".to_string(), content: "Content here".to_string(), timestamp: Utc::now(), source: "Bloomberg".to_string(), }; let event = MarketDataEvent::News(news.clone()); if let MarketDataEvent::News(n) = event { assert_eq!(n.symbol, news.symbol); assert_eq!(n.headline, news.headline); } else { panic!("Expected News variant"); } } #[test] fn test_market_data_event_timestamp_extraction_trade() { let timestamp = Utc::now(); let trade = TradeEvent { symbol: Symbol::new("AAPL".to_string()), price: Price::from_f64(150.00).unwrap(), quantity: Quantity::from_f64(100.0).unwrap(), side: OrderSide::Buy, timestamp, trade_id: "T1".to_string(), }; let event = MarketDataEvent::Trade(trade); assert_eq!(event.timestamp(), Some(timestamp)); } #[test] fn test_market_data_event_timestamp_extraction_quote() { let timestamp = Utc::now(); let quote = QuoteEvent { symbol: Symbol::new("GOOGL".to_string()), bid_price: Price::from_f64(2800.00).unwrap(), bid_quantity: Quantity::from_f64(50.0).unwrap(), ask_price: Price::from_f64(2800.50).unwrap(), ask_quantity: Quantity::from_f64(75.0).unwrap(), timestamp, }; let event = MarketDataEvent::Quote(quote); assert_eq!(event.timestamp(), Some(timestamp)); } #[test] fn test_market_data_event_timestamp_extraction_bar() { let timestamp = Utc::now(); let bar = BarEvent { symbol: Symbol::new("SPY".to_string()), open: Price::from_f64(450.00).unwrap(), high: Price::from_f64(451.00).unwrap(), low: Price::from_f64(449.00).unwrap(), close: Price::from_f64(450.50).unwrap(), volume: Quantity::from_f64(1000000.0).unwrap(), timestamp, interval: BarInterval::Minute1, }; let event = MarketDataEvent::Bar(bar); assert_eq!(event.timestamp(), Some(timestamp)); } #[test] fn test_market_data_event_timestamp_extraction_order_book() { let timestamp = Utc::now(); let order_book = OrderBookEvent { symbol: Symbol::new("BTC-USD".to_string()), bids: vec![], asks: vec![], timestamp, }; let event = MarketDataEvent::OrderBook(order_book); assert_eq!(event.timestamp(), Some(timestamp)); } #[test] fn test_market_data_event_timestamp_extraction_news() { let timestamp = Utc::now(); let news = NewsEvent { symbol: None, headline: "News".to_string(), content: "Content".to_string(), timestamp, source: "Source".to_string(), }; let event = MarketDataEvent::News(news); assert_eq!(event.timestamp(), Some(timestamp)); } #[test] fn test_market_data_event_serialization() { let trade = TradeEvent { symbol: Symbol::new("AAPL".to_string()), price: Price::from_f64(150.00).unwrap(), quantity: Quantity::from_f64(100.0).unwrap(), side: OrderSide::Buy, timestamp: Utc::now(), trade_id: "T1".to_string(), }; let event = MarketDataEvent::Trade(trade); let json = serde_json::to_string(&event).expect("Failed to serialize"); let deserialized: MarketDataEvent = serde_json::from_str(&json).expect("Failed to deserialize"); if let MarketDataEvent::Trade(t) = deserialized { assert_eq!(t.symbol, Symbol::new("AAPL".to_string())); } else { panic!("Expected Trade variant after deserialization"); } }