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
use crate::errors::PriceLevelError;
use crate::execution::list::TradeList;
use crate::execution::trade::Trade;
use crate::orders::Id;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;

/// Represents the result of a matching operation.
///
/// Fields are private to enforce invariant consistency between
/// `remaining_quantity`, `is_complete`, and `trades`.
/// Use the provided accessor methods and mutation helpers.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatchResult {
    /// The ID of the incoming order that initiated the match
    order_id: Id,

    /// List of trades that resulted from the match
    trades: TradeList,

    /// Remaining quantity of the incoming order after matching
    remaining_quantity: u64,

    /// Whether the order was completely filled
    is_complete: bool,

    /// Any orders that were completely filled and removed from the book
    filled_order_ids: Vec<Id>,
}

impl MatchResult {
    /// Create a new empty match result
    #[must_use]
    pub fn new(order_id: Id, initial_quantity: u64) -> Self {
        Self {
            order_id,
            trades: TradeList::new(),
            remaining_quantity: initial_quantity,
            is_complete: false,
            filled_order_ids: Vec::new(),
        }
    }

    /// Add a trade to this match result.
    pub fn add_trade(&mut self, trade: Trade) -> Result<(), PriceLevelError> {
        self.remaining_quantity = self
            .remaining_quantity
            .checked_sub(trade.quantity().as_u64())
            .ok_or_else(|| PriceLevelError::InvalidOperation {
                message: format!(
                    "trade quantity {} exceeds remaining quantity {}",
                    trade.quantity().as_u64(),
                    self.remaining_quantity
                ),
            })?;
        self.is_complete = self.remaining_quantity == 0;
        self.trades.add(trade);
        Ok(())
    }

    /// Add a filled order ID to track orders removed from the book
    pub fn add_filled_order_id(&mut self, order_id: Id) {
        self.filled_order_ids.push(order_id);
    }

    /// Returns the ID of the incoming order that initiated the match.
    #[must_use]
    pub fn order_id(&self) -> Id {
        self.order_id
    }

    /// Returns a reference to the list of trades.
    #[must_use]
    pub fn trades(&self) -> &TradeList {
        &self.trades
    }

    /// Returns the remaining quantity of the incoming order after matching.
    #[must_use]
    pub fn remaining_quantity(&self) -> u64 {
        self.remaining_quantity
    }

    /// Returns whether the order was completely filled.
    #[must_use]
    pub fn is_complete(&self) -> bool {
        self.is_complete
    }

    /// Returns the IDs of orders that were completely filled during matching.
    #[must_use]
    pub fn filled_order_ids(&self) -> &[Id] {
        &self.filled_order_ids
    }

    /// Sets the final remaining quantity and completion flag.
    ///
    /// This is used internally by the matching engine after the matching loop.
    pub(crate) fn finalize(&mut self, remaining_quantity: u64) {
        self.remaining_quantity = remaining_quantity;
        self.is_complete = remaining_quantity == 0;
    }

    /// Get the total executed quantity
    pub fn executed_quantity(&self) -> Result<u64, PriceLevelError> {
        self.trades.as_vec().iter().try_fold(0u64, |acc, trade| {
            acc.checked_add(trade.quantity().as_u64()).ok_or_else(|| {
                PriceLevelError::InvalidOperation {
                    message: "executed quantity overflow".to_string(),
                }
            })
        })
    }

    /// Get the total value executed
    pub fn executed_value(&self) -> Result<u128, PriceLevelError> {
        self.trades.as_vec().iter().try_fold(0u128, |acc, trade| {
            let trade_value = trade
                .price()
                .as_u128()
                .checked_mul(u128::from(trade.quantity().as_u64()))
                .ok_or_else(|| PriceLevelError::InvalidOperation {
                    message: "executed value multiplication overflow".to_string(),
                })?;

            acc.checked_add(trade_value)
                .ok_or_else(|| PriceLevelError::InvalidOperation {
                    message: "executed value accumulation overflow".to_string(),
                })
        })
    }

    /// Calculate the average execution price
    pub fn average_price(&self) -> Result<Option<f64>, PriceLevelError> {
        let executed_qty = self.executed_quantity()?;
        if executed_qty == 0 {
            Ok(None)
        } else {
            Ok(Some(self.executed_value()? as f64 / executed_qty as f64))
        }
    }
}

impl fmt::Display for MatchResult {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "MatchResult:order_id={};remaining_quantity={};is_complete={}",
            self.order_id, self.remaining_quantity, self.is_complete
        )?;
        write!(f, ";trades={}", self.trades)?;
        write!(f, ";filled_order_ids=[")?;
        for (i, order_id) in self.filled_order_ids.iter().enumerate() {
            if i > 0 {
                write!(f, ",")?;
            }
            write!(f, "{order_id}")?;
        }
        write!(f, "]")
    }
}

impl FromStr for MatchResult {
    type Err = PriceLevelError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        fn find_next_field(s: &str, start_pos: usize) -> Result<(&str, usize), PriceLevelError> {
            let mut pos = start_pos;

            while pos < s.len() {
                if s[pos..].starts_with(';') {
                    let value = &s[start_pos..pos];
                    return Ok((value, pos + 1));
                }
                pos += 1;
            }

            if pos == s.len() {
                let value = &s[start_pos..pos];
                return Ok((value, pos));
            }

            Err(PriceLevelError::InvalidFormat)
        }
        if !s.starts_with("MatchResult:") {
            return Err(PriceLevelError::InvalidFormat);
        }

        let mut order_id_str = None;
        let mut remaining_quantity_str = None;
        let mut is_complete_str = None;
        let mut trades_str = None;
        let mut filled_order_ids_str = None;

        let mut pos = "MatchResult:".len();

        while pos < s.len() {
            let field_end = match s[pos..].find('=') {
                Some(idx) => pos + idx,
                None => return Err(PriceLevelError::InvalidFormat),
            };

            let field_name = &s[pos..field_end];
            pos = field_end + 1;
            match field_name {
                "order_id" => {
                    let (value, next_pos) = find_next_field(s, pos)?;
                    order_id_str = Some(value);
                    pos = next_pos;
                }
                "remaining_quantity" => {
                    let (value, next_pos) = find_next_field(s, pos)?;
                    remaining_quantity_str = Some(value);
                    pos = next_pos;
                }
                "is_complete" => {
                    let (value, next_pos) = find_next_field(s, pos)?;
                    is_complete_str = Some(value);
                    pos = next_pos;
                }
                "trades" => {
                    if !s[pos..].starts_with("Trades:[") {
                        return Err(PriceLevelError::InvalidFormat);
                    }

                    let mut bracket_depth = 1;
                    let mut i = pos + "Trades:[".len();

                    while i < s.len() && bracket_depth > 0 {
                        if s[i..].starts_with(']') {
                            bracket_depth -= 1;
                            if bracket_depth == 0 {
                                break;
                            }
                            i += 1;
                        } else if s[i..].starts_with('[') {
                            bracket_depth += 1;
                            i += 1;
                        } else {
                            i += 1;
                        }
                    }

                    if bracket_depth > 0 {
                        return Err(PriceLevelError::InvalidFormat);
                    }

                    trades_str = Some(&s[pos..=i]);
                    pos = i + 1;
                    if pos < s.len() && s[pos..].starts_with(';') {
                        pos += 1;
                    } else if pos < s.len() {
                        return Err(PriceLevelError::InvalidFormat);
                    }
                }
                "filled_order_ids" => {
                    if !s[pos..].starts_with('[') {
                        return Err(PriceLevelError::InvalidFormat);
                    }

                    let mut bracket_depth = 1;
                    let mut i = pos + 1;

                    while i < s.len() && bracket_depth > 0 {
                        if s[i..].starts_with(']') {
                            bracket_depth -= 1;
                            if bracket_depth == 0 {
                                break;
                            }
                            i += 1;
                        } else if s[i..].starts_with('[') {
                            bracket_depth += 1;
                            i += 1;
                        } else {
                            i += 1;
                        }
                    }

                    if bracket_depth > 0 {
                        return Err(PriceLevelError::InvalidFormat);
                    }

                    filled_order_ids_str = Some(&s[pos..=i]);

                    pos = i + 1;
                    if pos < s.len() && s[pos..].starts_with(';') {
                        pos += 1;
                    }
                }
                _ => return Err(PriceLevelError::InvalidFormat),
            }
        }

        let order_id_str =
            order_id_str.ok_or_else(|| PriceLevelError::MissingField("order_id".to_string()))?;
        let remaining_quantity_str = remaining_quantity_str
            .ok_or_else(|| PriceLevelError::MissingField("remaining_quantity".to_string()))?;
        let is_complete_str = is_complete_str
            .ok_or_else(|| PriceLevelError::MissingField("is_complete".to_string()))?;
        let trades_str =
            trades_str.ok_or_else(|| PriceLevelError::MissingField("trades".to_string()))?;
        let filled_order_ids_str = filled_order_ids_str
            .ok_or_else(|| PriceLevelError::MissingField("filled_order_ids".to_string()))?;

        let order_id =
            Id::from_str(order_id_str).map_err(|_| PriceLevelError::InvalidFieldValue {
                field: "order_id".to_string(),
                value: order_id_str.to_string(),
            })?;

        let remaining_quantity = remaining_quantity_str.parse::<u64>().map_err(|_| {
            PriceLevelError::InvalidFieldValue {
                field: "remaining_quantity".to_string(),
                value: remaining_quantity_str.to_string(),
            }
        })?;

        let is_complete =
            is_complete_str
                .parse::<bool>()
                .map_err(|_| PriceLevelError::InvalidFieldValue {
                    field: "is_complete".to_string(),
                    value: is_complete_str.to_string(),
                })?;

        let trades = TradeList::from_str(trades_str)?;

        let filled_order_ids = if filled_order_ids_str == "[]" {
            Vec::new()
        } else {
            let content = &filled_order_ids_str[1..filled_order_ids_str.len() - 1];

            if content.is_empty() {
                Vec::new()
            } else {
                content
                    .split(',')
                    .map(|id_str| {
                        Id::from_str(id_str).map_err(|_| PriceLevelError::InvalidFieldValue {
                            field: "filled_order_ids".to_string(),
                            value: id_str.to_string(),
                        })
                    })
                    .collect::<Result<Vec<Id>, PriceLevelError>>()?
            }
        };

        Ok(MatchResult {
            order_id,
            trades,
            remaining_quantity,
            is_complete,
            filled_order_ids,
        })
    }
}