orderbook-rs 0.10.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
use orderbook_rs::OrderBook;
use orderbook_rs::orderbook::modifications::OrderQuantity;
use pricelevel::{
    Hash32, Id, OrderType, OrderUpdate, PegReferenceType, Price, Quantity, Side, TimeInForce,
    TimestampMs,
};

#[derive(Clone, Debug, Default, PartialEq)]
struct TestExtraFields {
    pub user_id: String,
    pub strategy: String,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_order_type_quantity_methods() {
        // Test quantity() method for different order types
        let standard_order = OrderType::Standard {
            id: Id::new_uuid(),
            price: Price::new(1000),
            quantity: Quantity::new(100),
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            extra_fields: TestExtraFields::default(),
        };
        assert_eq!(standard_order.quantity(), 100);

        let reserve_order = OrderType::ReserveOrder {
            id: Id::new_uuid(),
            price: Price::new(1000),
            visible_quantity: Quantity::new(30),
            hidden_quantity: Quantity::new(70),
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            replenish_threshold: Quantity::new(10),
            replenish_amount: Some(std::num::NonZeroU64::new(20).expect("nonzero")),
            auto_replenish: true,
            extra_fields: TestExtraFields::default(),
        };
        assert_eq!(reserve_order.quantity(), 30); // Returns visible quantity

        let post_only_order = OrderType::PostOnly {
            id: Id::new_uuid(),
            price: Price::new(1000),
            quantity: Quantity::new(75),
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            extra_fields: TestExtraFields::default(),
        };
        assert_eq!(post_only_order.quantity(), 75);

        let trailing_stop_order = OrderType::TrailingStop {
            id: Id::new_uuid(),
            price: Price::new(1000),
            quantity: Quantity::new(25),
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            trail_amount: Quantity::new(5),
            last_reference_price: Price::new(995),
            extra_fields: TestExtraFields::default(),
        };
        assert_eq!(trailing_stop_order.quantity(), 25);

        let pegged_order = OrderType::PeggedOrder {
            id: Id::new_uuid(),
            price: Price::new(1000),
            quantity: Quantity::new(80),
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            reference_price_offset: 5,
            reference_price_type: PegReferenceType::BestBid,
            extra_fields: TestExtraFields::default(),
        };
        assert_eq!(pegged_order.quantity(), 80);

        let market_to_limit_order = OrderType::MarketToLimit {
            id: Id::new_uuid(),
            price: Price::new(1000),
            quantity: Quantity::new(60),
            side: Side::Sell,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Ioc,
            timestamp: TimestampMs::new(0),
            extra_fields: TestExtraFields::default(),
        };
        assert_eq!(market_to_limit_order.quantity(), 60);

        let reserve_order = OrderType::ReserveOrder {
            id: Id::new_uuid(),
            price: Price::new(1000),
            visible_quantity: Quantity::new(30),
            hidden_quantity: Quantity::new(70),
            replenish_threshold: Quantity::new(10),
            replenish_amount: Some(std::num::NonZeroU64::new(20).expect("nonzero")),
            auto_replenish: true,
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            extra_fields: TestExtraFields::default(),
        };
        assert_eq!(reserve_order.quantity(), 30); // Returns visible quantity
    }

    #[test]
    fn test_order_type_total_quantity_methods() {
        // Test total_quantity() method for different order types
        let iceberg_order = OrderType::IcebergOrder {
            id: Id::new_uuid(),
            price: Price::new(1000),
            visible_quantity: Quantity::new(50),
            hidden_quantity: Quantity::new(150),
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            extra_fields: TestExtraFields::default(),
        };
        assert_eq!(iceberg_order.total_quantity(), 200); // visible + hidden

        let reserve_order = OrderType::ReserveOrder {
            id: Id::new_uuid(),
            price: Price::new(1000),
            visible_quantity: Quantity::new(30),
            hidden_quantity: Quantity::new(70),
            replenish_threshold: Quantity::new(10),
            replenish_amount: Some(std::num::NonZeroU64::new(20).expect("nonzero")),
            auto_replenish: true,
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            extra_fields: TestExtraFields::default(),
        };
        assert_eq!(reserve_order.total_quantity(), 100); // visible + hidden

        let standard_order = OrderType::Standard {
            id: Id::new_uuid(),
            price: Price::new(1000),
            quantity: Quantity::new(100),
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            extra_fields: TestExtraFields::default(),
        };
        assert_eq!(standard_order.total_quantity(), 100);
    }

    #[test]
    fn test_order_type_set_quantity_methods() {
        // Test set_quantity() method for different order types
        let mut standard_order = OrderType::Standard {
            id: Id::new_uuid(),
            price: Price::new(1000),
            quantity: Quantity::new(100),
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            extra_fields: TestExtraFields::default(),
        };
        standard_order.set_quantity(80);
        assert_eq!(standard_order.quantity(), 80);

        let mut iceberg_order = OrderType::IcebergOrder {
            id: Id::new_uuid(),
            price: Price::new(1000),
            visible_quantity: Quantity::new(50),
            hidden_quantity: Quantity::new(150),
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            extra_fields: TestExtraFields::default(),
        };
        iceberg_order.set_quantity(40);
        assert_eq!(iceberg_order.quantity(), 40); // visible quantity updated

        let mut reserve_order = OrderType::ReserveOrder {
            id: Id::new_uuid(),
            price: Price::new(1000),
            visible_quantity: Quantity::new(30),
            hidden_quantity: Quantity::new(70),
            replenish_threshold: Quantity::new(10),
            replenish_amount: Some(std::num::NonZeroU64::new(20).expect("nonzero")),
            auto_replenish: true,
            side: Side::Buy,
            user_id: Hash32::zero(),
            time_in_force: TimeInForce::Gtc,
            timestamp: TimestampMs::new(0),
            extra_fields: TestExtraFields::default(),
        };
        reserve_order.set_quantity(80); // Reduce from 100 to 80
        assert_eq!(reserve_order.quantity(), 10); // visible reduced by 20
        assert_eq!(reserve_order.total_quantity(), 80); // total is now 80
    }

    #[test]
    fn test_update_order_price_same_value() {
        let book: OrderBook<TestExtraFields> = OrderBook::new("TEST");
        let order_id = Id::new_uuid();

        // Add an order
        book.add_limit_order(order_id, 1000, 100, Side::Buy, TimeInForce::Gtc, None)
            .unwrap();

        // Try to update to the same price
        let update = OrderUpdate::UpdatePrice {
            order_id,
            new_price: Price::new(1000),
        };

        let result = book.update_order(update);
        assert!(result.is_err());
        if let Err(e) = result {
            assert!(
                e.to_string()
                    .contains("Cannot update price to the same value")
            );
        }
    }

    #[test]
    fn test_update_order_price_success() {
        let book: OrderBook<TestExtraFields> = OrderBook::new("TEST");
        let order_id = Id::new_uuid();

        // Add an order
        book.add_limit_order(order_id, 1000, 100, Side::Buy, TimeInForce::Gtc, None)
            .unwrap();

        // Update the price
        let update = OrderUpdate::UpdatePrice {
            order_id,
            new_price: Price::new(1010),
        };

        let result = book.update_order(update);
        assert!(result.is_ok());
        let updated_order = result.unwrap();
        assert!(updated_order.is_some());

        // Verify the order was updated
        let order = updated_order.unwrap();
        assert_eq!(order.price().as_u128(), 1010);
        assert_eq!(order.visible_quantity(), Quantity::new(100));
    }

    #[test]
    fn test_update_order_quantity_success() {
        let book: OrderBook<TestExtraFields> = OrderBook::new("TEST");
        let order_id = Id::new_uuid();

        // Add an order
        book.add_limit_order(order_id, 1000, 100, Side::Buy, TimeInForce::Gtc, None)
            .unwrap();

        // Update the quantity
        let update = OrderUpdate::UpdateQuantity {
            order_id,
            new_quantity: Quantity::new(80),
        };

        let result = book.update_order(update);
        assert!(result.is_ok());
        let updated_order = result.unwrap();
        assert!(updated_order.is_some());

        // Verify the order was updated
        let order = updated_order.unwrap();
        assert_eq!(order.price().as_u128(), 1000);
        assert_eq!(order.visible_quantity(), Quantity::new(80));
    }

    #[test]
    fn test_update_order_price_and_quantity_success() {
        let book: OrderBook<TestExtraFields> = OrderBook::new("TEST");
        let order_id = Id::new_uuid();

        // Add an order
        book.add_limit_order(order_id, 1000, 100, Side::Buy, TimeInForce::Gtc, None)
            .unwrap();

        // Update both price and quantity
        let update = OrderUpdate::UpdatePriceAndQuantity {
            order_id,
            new_price: Price::new(1020),
            new_quantity: Quantity::new(75),
        };

        let result = book.update_order(update);
        assert!(result.is_ok());
        let updated_order = result.unwrap();
        assert!(updated_order.is_some());

        // Verify the order was updated
        let order = updated_order.unwrap();
        assert_eq!(order.price().as_u128(), 1020);
        assert_eq!(order.visible_quantity(), Quantity::new(75));
    }

    #[test]
    fn test_update_nonexistent_order() {
        let book: OrderBook<TestExtraFields> = OrderBook::new("TEST");
        let nonexistent_id = Id::new_uuid();

        // Try to update a nonexistent order
        let update = OrderUpdate::UpdatePrice {
            order_id: nonexistent_id,
            new_price: Price::new(1000),
        };

        let result = book.update_order(update);
        assert!(result.is_ok());
        assert!(result.unwrap().is_none());
    }
}