pricelevel 0.7.0

A high-performance, lock-free price level implementation for limit order books in Rust. This library provides the building blocks for creating efficient trading systems with support for multiple order types and concurrent access patterns.
Documentation
#[cfg(test)]
mod tests {
    use crate::execution::list::TransactionList;
    use crate::execution::match_result::MatchResult;
    use crate::execution::transaction::Transaction;
    use crate::orders::OrderId;
    use crate::orders::Side;
    use std::str::FromStr;
    use tracing::info;
    use uuid::Uuid;

    // Helper function to create a test transaction
    fn create_test_transaction(
        id: Uuid,
        taker_id: u64,
        maker_id: u64,
        price: u128,
        quantity: u64,
    ) -> Transaction {
        Transaction {
            transaction_id: id,
            taker_order_id: OrderId::from_u64(taker_id),
            maker_order_id: OrderId::from_u64(maker_id),
            price,
            quantity,
            taker_side: Side::Buy,
            timestamp: 1616823000000, // + id, // Create unique timestamps
        }
    }

    #[test]
    fn test_match_result_new() {
        let result = MatchResult::new(OrderId::from_u64(123), 100);

        assert_eq!(result.order_id, OrderId::from_u64(123));
        assert_eq!(result.remaining_quantity, 100);
        assert!(!result.is_complete);
        assert!(result.transactions.is_empty());
        assert!(result.filled_order_ids.is_empty());
    }

    #[test]
    fn test_add_transaction() {
        let mut result = MatchResult::new(OrderId::from_u64(123), 100);

        // Add a transaction for 30 quantity
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        let transaction1 = create_test_transaction(uuid, 123, 456, 1000, 30);
        result.add_transaction(transaction1);

        assert_eq!(result.remaining_quantity, 70); // 100 - 30
        assert!(!result.is_complete);
        assert_eq!(result.transactions.len(), 1);

        // Add another transaction that will complete the match
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        let transaction2 = create_test_transaction(uuid, 123, 789, 1000, 70);
        result.add_transaction(transaction2);

        assert_eq!(result.remaining_quantity, 0);
        assert!(result.is_complete);
        assert_eq!(result.transactions.len(), 2);

        // Add a transaction that would exceed the remaining quantity
        // This is normally prevented by validation logic elsewhere, but testing the method
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        let transaction3 = create_test_transaction(uuid, 123, 101, 1000, 20);
        result.add_transaction(transaction3);

        // Should remain at 0 due to saturating_sub
        assert_eq!(result.remaining_quantity, 0);
        assert!(result.is_complete);
        assert_eq!(result.transactions.len(), 3);
    }

    #[test]
    fn test_add_filled_order_id() {
        let mut result = MatchResult::new(OrderId::from_u64(123), 100);

        result.add_filled_order_id(OrderId::from_u64(456));
        result.add_filled_order_id(OrderId::from_u64(789));

        assert_eq!(result.filled_order_ids.len(), 2);
        assert_eq!(result.filled_order_ids[0], OrderId::from_u64(456));
        assert_eq!(result.filled_order_ids[1], OrderId::from_u64(789));
    }

    #[test]
    fn test_executed_quantity() {
        let mut result = MatchResult::new(OrderId::from_u64(123), 100);

        // No transactions yet
        assert_eq!(result.executed_quantity(), 0);

        // Add some transactions
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        result.add_transaction(create_test_transaction(uuid, 123, 456, 1000, 30));
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        result.add_transaction(create_test_transaction(uuid, 123, 789, 1000, 20));

        assert_eq!(result.executed_quantity(), 50); // 30 + 20
    }

    #[test]
    fn test_executed_value() {
        let mut result = MatchResult::new(OrderId::from_u64(123), 100);

        // No transactions yet
        assert_eq!(result.executed_value(), 0);

        // Add transactions with different prices
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        result.add_transaction(create_test_transaction(uuid, 123, 456, 1000, 30)); // Value: 30,000
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        result.add_transaction(create_test_transaction(uuid, 123, 789, 1200, 20)); // Value: 24,000

        assert_eq!(result.executed_value(), 54000); // 30,000 + 24,000
    }

    #[test]
    fn test_average_price() {
        let mut result = MatchResult::new(OrderId::from_u64(123), 100);

        // No transactions yet
        assert_eq!(result.average_price(), None);

        // Add transactions with different prices
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        result.add_transaction(create_test_transaction(uuid, 123, 456, 1000, 30)); // Value: 30,000
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        result.add_transaction(create_test_transaction(uuid, 123, 789, 1200, 20)); // Value: 24,000

        // Average price: 54,000 / 50 = 1,080
        assert_eq!(result.average_price(), Some(1080.0));
    }

    #[test]
    fn test_display() {
        let mut result = MatchResult::new(OrderId::from_u64(123), 100);

        // Test display with empty transactions and filled_order_ids
        let display_str = result.to_string();

        assert!(
            display_str
                .starts_with("MatchResult:order_id=00000000-0000-007b-0000-000000000000;remaining_quantity=100;is_complete=false")
        );
        assert!(display_str.contains("transactions=Transactions:[]"));
        assert!(display_str.contains("filled_order_ids=[]"));

        // Add some transactions and filled order IDs
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        result.add_transaction(create_test_transaction(uuid, 123, 456, 1000, 30));
        result.add_filled_order_id(OrderId::from_u64(456));

        let display_str = result.to_string();
        assert!(
            display_str
                .starts_with("MatchResult:order_id=00000000-0000-007b-0000-000000000000;remaining_quantity=70;is_complete=false")
        );
        assert!(
            display_str.contains("Transaction:transaction_id=6ba7b810-9dad-11d1-80b4-00c04fd430c8")
        );
        assert!(display_str.contains("filled_order_ids=[00000000-0000-01c8-0000-000000000000]"));
    }

    #[test]
    fn test_from_str_valid() {
        let input = "MatchResult:order_id=00000000-0000-007b-0000-000000000000;remaining_quantity=70;is_complete=false;transactions=Transactions:[];filled_order_ids=[]";
        let result = match MatchResult::from_str(input) {
            Ok(r) => r,
            Err(e) => {
                panic!("Test failed: {e:?}");
            }
        };

        assert_eq!(result.order_id, OrderId::from_u64(123));
        assert_eq!(result.remaining_quantity, 70);
        assert!(!result.is_complete);
        assert!(result.transactions.is_empty());
        assert!(result.filled_order_ids.is_empty());

        // Test parsing with transactions and filled order IDs
        let input = "MatchResult:order_id=00000000-0000-007b-0000-000000000000;remaining_quantity=70;is_complete=false;transactions=Transactions:[Transaction:transaction_id=6ba7b810-9dad-11d1-80b4-00c04fd430c8;taker_order_id=00000000-0000-007b-0000-000000000000;maker_order_id=00000000-0000-01c8-0000-000000000000;price=1000;quantity=30;taker_side=BUY;timestamp=1616823000001];filled_order_ids=[00000000-0000-01c8-0000-000000000000]";
        let result = MatchResult::from_str(input).unwrap();

        assert_eq!(result.order_id, OrderId::from_u64(123));
        assert_eq!(result.remaining_quantity, 70);
        assert!(!result.is_complete);
        assert_eq!(result.transactions.len(), 1);
        assert_eq!(result.filled_order_ids.len(), 1);
        assert_eq!(result.filled_order_ids[0], OrderId::from_u64(456));
    }

    #[test]
    fn test_from_str_invalid_format() {
        // Test invalid prefix
        let input = "InvalidPrefix:order_id=123;remaining_quantity=70;is_complete=false;transactions=Transactions:[];filled_order_ids=[]";
        let result = MatchResult::from_str(input);
        assert!(result.is_err());

        // Test missing field
        let input =
            "MatchResult:order_id=123;remaining_quantity=70;is_complete=false;filled_order_ids=[]";
        let result = MatchResult::from_str(input);
        assert!(result.is_err());

        // Test invalid value type
        let input = "MatchResult:order_id=abc;remaining_quantity=70;is_complete=false;transactions=Transactions:[];filled_order_ids=[]";
        let result = MatchResult::from_str(input);
        assert!(result.is_err());

        // Test invalid boolean
        let input = "MatchResult:order_id=123;remaining_quantity=70;is_complete=invalidbool;transactions=Transactions:[];filled_order_ids=[]";
        let result = MatchResult::from_str(input);
        assert!(result.is_err());

        // Test invalid filled_order_ids format
        let input = "MatchResult:order_id=123;remaining_quantity=70;is_complete=false;transactions=Transactions:[];filled_order_ids=invalid";
        let result = MatchResult::from_str(input);
        assert!(result.is_err());
    }

    #[test]
    fn test_roundtrip() {
        // Create a match result with some data
        let mut original = MatchResult::new(OrderId::from_u64(123), 100);
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        original.add_transaction(create_test_transaction(uuid, 123, 456, 1000, 30));
        let uuid = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
        original.add_transaction(create_test_transaction(uuid, 123, 789, 1200, 20));
        original.add_filled_order_id(OrderId::from_u64(456));
        original.add_filled_order_id(OrderId::from_u64(789));

        // Convert to string
        let string_representation = original.to_string();
        info!("String generate: '{}'", string_representation);

        // Parse back
        let parsed = match MatchResult::from_str(&string_representation) {
            Ok(r) => r,
            Err(e) => {
                panic!("Test failed: {e:?}");
            }
        };

        // Verify all fields match
        assert_eq!(parsed.order_id, original.order_id);
        assert_eq!(parsed.remaining_quantity, original.remaining_quantity);
        assert_eq!(parsed.is_complete, original.is_complete);
        assert_eq!(parsed.filled_order_ids, original.filled_order_ids);

        // Verify transactions (need to check each one since Transaction might not implement PartialEq)
        assert_eq!(parsed.transactions.len(), original.transactions.len());
        for (i, transaction) in original.transactions.as_vec().iter().enumerate() {
            let parsed_transaction = &parsed.transactions.as_vec()[i];
            assert_eq!(
                parsed_transaction.transaction_id,
                transaction.transaction_id
            );
            assert_eq!(
                parsed_transaction.taker_order_id,
                transaction.taker_order_id
            );
            assert_eq!(
                parsed_transaction.maker_order_id,
                transaction.maker_order_id
            );
            assert_eq!(parsed_transaction.price, transaction.price);
            assert_eq!(parsed_transaction.quantity, transaction.quantity);
            assert_eq!(parsed_transaction.taker_side, transaction.taker_side);
            assert_eq!(parsed_transaction.timestamp, transaction.timestamp);
        }
    }

    #[test]
    fn test_with_multiple_filled_order_ids() {
        // Create a match result with multiple filled order IDs
        let mut result = MatchResult::new(OrderId::from_u64(123), 100); // 00000000-0000-007b-0000-000000000000
        result.add_filled_order_id(OrderId::from_u64(456)); // 00000000-0000-01c8-0000-000000000000
        result.add_filled_order_id(OrderId::from_u64(789)); // 00000000-0000-0315-0000-000000000000
        result.add_filled_order_id(OrderId::from_u64(101)); // 00000000-0000-0065-0000-000000000000

        // Convert to string
        let string_representation = result.to_string();

        // Verify filled_order_ids format
        assert!(string_representation.contains("filled_order_ids=[00000000-0000-01c8-0000-000000000000,00000000-0000-0315-0000-000000000000,00000000-0000-0065-0000-000000000000]"));

        // Parse back
        let parsed = MatchResult::from_str(&string_representation).unwrap();

        // Verify filled_order_ids were parsed correctly
        assert_eq!(parsed.filled_order_ids.len(), 3);
        assert_eq!(parsed.filled_order_ids[0], OrderId::from_u64(456));
        assert_eq!(parsed.filled_order_ids[1], OrderId::from_u64(789));
        assert_eq!(parsed.filled_order_ids[2], OrderId::from_u64(101));
    }

    #[test]
    fn test_with_empty_transactions_and_filled_ids() {
        // Test with explicitly empty collections
        let mut result = MatchResult::new(OrderId::from_u64(123), 100);
        result.transactions = TransactionList::new(); // Explicitly empty
        result.filled_order_ids = Vec::new(); // Explicitly empty

        // Convert to string
        let string_representation = result.to_string();

        // Parse back
        let parsed = MatchResult::from_str(&string_representation).unwrap();

        // Verify
        assert!(parsed.transactions.is_empty());
        assert!(parsed.filled_order_ids.is_empty());
    }

    #[test]
    fn test_match_result_from_str_parsing_edge_cases() {
        // Test parsing a complete match result with all fields
        let input = "MatchResult:order_id=00000000-0000-007b-0000-000000000000;remaining_quantity=70;is_complete=false;transactions=Transactions:[Transaction:transaction_id=6ba7b810-9dad-11d1-80b4-00c04fd430c8;taker_order_id=00000000-0000-007b-0000-000000000000;maker_order_id=00000000-0000-01c8-0000-000000000000;price=1000;quantity=30;taker_side=BUY;timestamp=1616823000000];filled_order_ids=[00000000-0000-01c8-0000-000000000000,00000000-0000-0315-0000-000000000000]";

        let result = MatchResult::from_str(input).unwrap();

        assert_eq!(result.order_id, OrderId::from_u64(123));
        assert_eq!(result.remaining_quantity, 70);
        assert!(!result.is_complete);
        assert_eq!(result.transactions.len(), 1);
        assert_eq!(result.filled_order_ids.len(), 2);
        assert_eq!(result.filled_order_ids[0], OrderId::from_u64(456));
        assert_eq!(result.filled_order_ids[1], OrderId::from_u64(789));

        // Test parsing with complex nested structures
        let input = "MatchResult:order_id=00000000-0000-007b-0000-000000000000;remaining_quantity=70;is_complete=false;transactions=Transactions:[Transaction:transaction_id=6ba7b810-9dad-11d1-80b4-00c04fd430c8;taker_order_id=00000000-0000-007b-0000-000000000000;maker_order_id=00000000-0000-01c8-0000-000000000000;price=1000;quantity=30;taker_side=BUY;timestamp=1616823000000,Transaction:transaction_id=7ca7b810-9dad-11d1-80b4-00c04fd430c8;taker_order_id=00000000-0000-007b-0000-000000000000;maker_order_id=00000000-0000-0315-0000-000000000000;price=1100;quantity=40;taker_side=BUY;timestamp=1616823000001];filled_order_ids=[00000000-0000-01c8-0000-000000000000,00000000-0000-0315-0000-000000000000]";

        let result = MatchResult::from_str(input).unwrap();

        assert_eq!(result.transactions.len(), 2);
        let transaction1 = &result.transactions.as_vec()[0];
        let transaction2 = &result.transactions.as_vec()[1];

        assert_eq!(transaction1.quantity, 30);
        assert_eq!(transaction2.quantity, 40);
    }

    #[test]
    fn test_match_result_parsing_error_cases() {
        // Test invalid field_name
        let input = "MatchResult:invalid_field=value;remaining_quantity=70;is_complete=false;transactions=Transactions:[];filled_order_ids=[]";
        let result = MatchResult::from_str(input);
        assert!(result.is_err());

        // Test bracket mismatch in transactions
        let input = "MatchResult:order_id=00000000-0000-007b-0000-000000000000;remaining_quantity=70;is_complete=false;transactions=Transactions:[Transaction:transaction_id=6ba7b810-9dad-11d1-80b4-00c04fd430c8;taker_order_id=00000000-0000-007b-0000-000000000000;filled_order_ids=[]";
        let result = MatchResult::from_str(input);
        assert!(result.is_err());

        // Test invalid transactions format
        let input = "MatchResult:order_id=00000000-0000-007b-0000-000000000000;remaining_quantity=70;is_complete=false;transactions=NotTransactions:[];filled_order_ids=[]";
        let result = MatchResult::from_str(input);
        assert!(result.is_err());

        // Test invalid filled_order_ids format
        let input = "MatchResult:order_id=00000000-0000-007b-0000-000000000000;remaining_quantity=70;is_complete=false;transactions=Transactions:[];filled_order_ids=NotAnArray";
        let result = MatchResult::from_str(input);
        assert!(result.is_err());
    }

    #[test]
    fn test_match_result_find_fields() {
        // Create a match result with simple field structure
        let mut result = MatchResult::new(OrderId::from_u64(123), 100);
        result.remaining_quantity = 50;
        result.is_complete = false;

        // Convert to string
        let string_representation = result.to_string();

        // Manually parse some fields to test the find_next_field function
        let order_id_pos = string_representation.find("order_id=").unwrap() + "order_id=".len();
        let semicolon_pos = string_representation[order_id_pos..].find(';').unwrap() + order_id_pos;
        let order_id_str = &string_representation[order_id_pos..semicolon_pos];

        assert_eq!(order_id_str, "00000000-0000-007b-0000-000000000000");

        let remaining_pos = string_representation.find("remaining_quantity=").unwrap()
            + "remaining_quantity=".len();
        let semicolon_pos =
            string_representation[remaining_pos..].find(';').unwrap() + remaining_pos;
        let remaining_str = &string_representation[remaining_pos..semicolon_pos];

        assert_eq!(remaining_str, "50");

        let complete_pos =
            string_representation.find("is_complete=").unwrap() + "is_complete=".len();
        let semicolon_pos = string_representation[complete_pos..].find(';').unwrap() + complete_pos;
        let complete_str = &string_representation[complete_pos..semicolon_pos];

        assert_eq!(complete_str, "false");
    }
}