diff --git a/src/book.rs b/src/book.rs index 5e3a8d8..a5910c4 100644 --- a/src/book.rs +++ b/src/book.rs @@ -796,7 +796,9 @@ impl OrderBook { #[cfg(test)] mod tests { use super::*; - use rust_decimal_macros::dec; // Convenient macro for creating Decimal literals + use rust_decimal_macros::dec; + use std::str::FromStr; + use std::time::Duration; // Convenient macro for creating Decimal literals #[test] fn test_order_book_creation() { @@ -929,26 +931,28 @@ mod tests { fn test_liquidity_analysis() { let mut book = OrderBook::new("test_token".to_string(), 10); - // Build order book - book.apply_delta_fast(7500, 1000); // bid at 0.75, size 100 - book.apply_delta_fast(7400, 500); // bid at 0.74, size 50 - book.apply_delta_fast(7600, 800); // ask at 0.76, size 80 - book.apply_delta_fast(7700, 1200); // ask at 0.77, size 120 + // Build order book using legacy methods + book.apply_bid_delta(Decimal::from_str("0.75").unwrap(), Decimal::from_str("100.0").unwrap()); + book.apply_bid_delta(Decimal::from_str("0.74").unwrap(), Decimal::from_str("50.0").unwrap()); + book.apply_ask_delta(Decimal::from_str("0.76").unwrap(), Decimal::from_str("80.0").unwrap()); + book.apply_ask_delta(Decimal::from_str("0.77").unwrap(), Decimal::from_str("120.0").unwrap()); - // Test liquidity at specific price - let bid_liquidity = book.liquidity_at_price(Decimal::from_str("0.75").unwrap()); + // Test liquidity at specific price (bids) + let bid_liquidity = book.liquidity_at_price(Decimal::from_str("0.75").unwrap(), Side::BUY); assert_eq!(bid_liquidity, Decimal::from_str("100.0").unwrap()); - let ask_liquidity = book.liquidity_at_price(Decimal::from_str("0.76").unwrap()); + // Test liquidity at specific price (asks) + let ask_liquidity = book.liquidity_at_price(Decimal::from_str("0.76").unwrap(), Side::SELL); assert_eq!(ask_liquidity, Decimal::from_str("80.0").unwrap()); - // Test liquidity in range + // Test liquidity in range (both sides) let range_liquidity = book.liquidity_in_range( Decimal::from_str("0.74").unwrap(), - Decimal::from_str("0.77").unwrap() + Decimal::from_str("0.77").unwrap(), + Side::BUY ); - // Should include: 50 (0.74 bid) + 100 (0.75 bid) + 80 (0.76 ask) + 120 (0.77 ask) = 350 - assert_eq!(range_liquidity, Decimal::from_str("350.0").unwrap()); + // Should include bid liquidity: 50 (0.74 bid) + 100 (0.75 bid) = 150 + assert_eq!(range_liquidity, Decimal::from_str("150.0").unwrap()); } #[test] @@ -959,12 +963,12 @@ mod tests { assert!(book.is_valid()); // Add normal levels - book.apply_delta_fast(7500, 1000); // bid at 0.75 - book.apply_delta_fast(7600, 800); // ask at 0.76 + book.apply_bid_delta(Decimal::from_str("0.75").unwrap(), Decimal::from_str("100.0").unwrap()); + book.apply_ask_delta(Decimal::from_str("0.76").unwrap(), Decimal::from_str("80.0").unwrap()); assert!(book.is_valid()); // Create crossed book (invalid) - bid higher than ask - book.apply_delta_fast(7700, 500); // bid at 0.77 (higher than ask at 0.76) + book.apply_bid_delta(Decimal::from_str("0.77").unwrap(), Decimal::from_str("50.0").unwrap()); assert!(!book.is_valid()); } diff --git a/src/fill.rs b/src/fill.rs index aba5516..12f656e 100644 --- a/src/fill.rs +++ b/src/fill.rs @@ -564,4 +564,146 @@ mod tests { assert!(processor.process_fill(fill).is_ok()); assert_eq!(processor.pending_fills.len(), 1); } + + #[test] + fn test_fill_engine_market_order_execution() { + let mut engine = FillEngine::new(); + + // Create a market order + let market_order = MarketOrder { + id: "market_1".to_string(), + side: Side::BUY, + size: dec!(50), + token_id: "token_1".to_string(), + max_slippage: Some(dec!(0.05)), // 5% slippage tolerance + }; + + // Execute the market order + let result = engine.execute_market_order(market_order); + assert!(result.is_ok()); + + let execution = result.unwrap(); + assert_eq!(execution.order_id, "market_1"); + assert_eq!(execution.side, Side::BUY); + assert_eq!(execution.requested_size, dec!(50)); + } + + #[test] + fn test_fill_processor_batch_processing() { + let mut processor = FillProcessor::new(); + + // Add multiple fills + let fill1 = Fill { + id: "fill_1".to_string(), + order_id: "order_1".to_string(), + side: Side::BUY, + size: dec!(25), + price: dec!(0.75), + fee: dec!(0.01), + timestamp: 1234567890, + token_id: "token_1".to_string(), + }; + + let fill2 = Fill { + id: "fill_2".to_string(), + order_id: "order_2".to_string(), + side: Side::SELL, + size: dec!(30), + price: dec!(0.76), + fee: dec!(0.015), + timestamp: 1234567891, + token_id: "token_1".to_string(), + }; + + processor.add_fill(fill1); + processor.add_fill(fill2); + + // Process all fills + processor.process_pending_fills(); + + assert_eq!(processor.stats.processed_fills, 2); + assert_eq!(processor.stats.processed_volume, dec!(55)); // 25 + 30 + assert_eq!(processor.stats.pending_fills, 0); + } + + #[test] + fn test_market_order_validation() { + let valid_order = MarketOrder { + id: "valid_1".to_string(), + side: Side::BUY, + size: dec!(10), + token_id: "0x1234567890123456789012345678901234567890".to_string(), + max_slippage: Some(dec!(0.1)), + }; + + let result = validate_market_order(&valid_order); + assert!(result.is_ok()); + + // Test invalid order (zero size) + let invalid_order = MarketOrder { + id: "invalid_1".to_string(), + side: Side::BUY, + size: Decimal::ZERO, + token_id: "0x1234567890123456789012345678901234567890".to_string(), + max_slippage: Some(dec!(0.1)), + }; + + let result = validate_market_order(&invalid_order); + assert!(result.is_err()); + } + + #[test] + fn test_fee_calculation_edge_cases() { + // Test very small amounts + let small_fee = calculate_fee(dec!(0.001), dec!(0.005)); + assert!(small_fee >= Decimal::ZERO); + + // Test large amounts + let large_fee = calculate_fee(dec!(10000), dec!(0.005)); + assert_eq!(large_fee, dec!(50)); // 10000 * 0.005 + + // Test zero fee rate + let zero_fee = calculate_fee(dec!(100), Decimal::ZERO); + assert_eq!(zero_fee, Decimal::ZERO); + + // Test zero amount + let zero_amount_fee = calculate_fee(Decimal::ZERO, dec!(0.005)); + assert_eq!(zero_amount_fee, Decimal::ZERO); + } + + #[test] + fn test_fill_processor_statistics() { + let mut processor = FillProcessor::new(); + + // Initial stats should be zero + assert_eq!(processor.stats.pending_fills, 0); + assert_eq!(processor.stats.processed_fills, 0); + assert_eq!(processor.stats.pending_volume, Decimal::ZERO); + assert_eq!(processor.stats.processed_volume, Decimal::ZERO); + + // Add a fill + let fill = Fill { + id: "fill_stats".to_string(), + order_id: "order_stats".to_string(), + side: Side::BUY, + size: dec!(75), + price: dec!(0.80), + fee: dec!(0.02), + timestamp: 1234567892, + token_id: "token_1".to_string(), + }; + + processor.add_fill(fill); + + // Check pending stats + assert_eq!(processor.stats.pending_fills, 1); + assert_eq!(processor.stats.pending_volume, dec!(75)); + + // Process and check final stats + processor.process_pending_fills(); + assert_eq!(processor.stats.processed_fills, 1); + assert_eq!(processor.stats.processed_volume, dec!(75)); + assert_eq!(processor.stats.pending_fills, 0); + assert_eq!(processor.stats.pending_volume, Decimal::ZERO); + } } \ No newline at end of file diff --git a/tarpaulin-report.html b/tarpaulin-report.html deleted file mode 100644 index 0112be4..0000000 --- a/tarpaulin-report.html +++ /dev/null @@ -1,737 +0,0 @@ - - -
- - - - - - - - - - - \ No newline at end of file