Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pricelevel"
version = "0.6.0"
version = "0.7.0"
edition = "2024"
authors = ["Joaquin Bejar <jb@taunais.com>"]
description = "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."
Expand Down Expand Up @@ -28,15 +28,15 @@ include = [
]

[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3" }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
crossbeam = "0.8"
uuid = { version = "1.20", features = ["v4", "v5", "serde"] }
ulid = { version = "1.2", features = ["serde"] }
dashmap = "6.1"
sha2 = "0.10"
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true, features = ["derive"] }
crossbeam = { workspace = true }
uuid = { workspace = true, features = ["v4", "v5", "serde"] }
ulid = { workspace = true, features = ["serde"] }
dashmap = { workspace = true }
sha2 = { workspace = true }


[dev-dependencies]
Expand All @@ -63,4 +63,12 @@ members = [

[workspace.dependencies]
pricelevel = { path = "." }
tracing = "0.1.41"
tracing = "0.1"
tracing-subscriber = { version = "0.3" }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
crossbeam = "0.8"
uuid = { version = "1.21", features = ["v4", "v5", "serde"] }
ulid = { version = "1.2", features = ["serde"] }
dashmap = "6.1"
sha2 = "0.10"
99 changes: 51 additions & 48 deletions src/execution/list.rs
Original file line number Diff line number Diff line change
@@ -1,78 +1,81 @@
use crate::errors::PriceLevelError;
use crate::execution::transaction::Transaction;
use crate::execution::transaction::Trade;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;

/// A wrapper for a vector of transactions to implement custom serialization
/// A wrapper for a vector of trades to implement custom serialization
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TransactionList {
pub transactions: Vec<Transaction>,
pub struct TradeList {
/// Ordered collection of trades.
pub trades: Vec<Trade>,
}

impl TransactionList {
/// Create a new empty transaction list
impl TradeList {
/// Create a new empty trade list
pub fn new() -> Self {
Self {
transactions: Vec::new(),
}
Self { trades: Vec::new() }
}

/// Create a transaction list from an existing vector
pub fn from_vec(transactions: Vec<Transaction>) -> Self {
Self { transactions }
/// Create a trade list from an existing vector
pub fn from_vec(trades: Vec<Trade>) -> Self {
Self { trades }
}

/// Add a transaction to the list
pub fn add(&mut self, transaction: Transaction) {
self.transactions.push(transaction);
/// Add a trade to the list
pub fn add(&mut self, trade: Trade) {
self.trades.push(trade);
}

/// Get a reference to the underlying vector
pub fn as_vec(&self) -> &Vec<Transaction> {
&self.transactions
pub fn as_vec(&self) -> &Vec<Trade> {
&self.trades
}

/// Convert into a vector of transactions
pub fn into_vec(self) -> Vec<Transaction> {
self.transactions
/// Convert into a vector of trades
pub fn into_vec(self) -> Vec<Trade> {
self.trades
}

/// Returns `true` when the list does not contain any trades.
#[must_use]
pub fn is_empty(&self) -> bool {
self.transactions.is_empty()
self.trades.is_empty()
}

/// Returns the number of trades in the list.
#[must_use]
pub fn len(&self) -> usize {
self.transactions.len()
self.trades.len()
}
}

impl Default for TransactionList {
impl Default for TradeList {
fn default() -> Self {
Self::new()
}
}

impl fmt::Display for TransactionList {
impl fmt::Display for TradeList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Transactions:[")?;
write!(f, "Trades:[")?;

for (i, transaction) in self.transactions.iter().enumerate() {
for (i, trade) in self.trades.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
write!(f, "{transaction}")?;
write!(f, "{trade}")?;
}

write!(f, "]")
}
}

impl FromStr for TransactionList {
impl FromStr for TradeList {
type Err = PriceLevelError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.starts_with("Transactions:[") || !s.ends_with("]") {
if !s.starts_with("Trades:[") || !s.ends_with("]") {
return Err(PriceLevelError::InvalidFormat);
}

Expand All @@ -86,51 +89,51 @@ impl FromStr for TransactionList {
let content = &s[content_start + 1..content_end];

if content.is_empty() {
return Ok(TransactionList::new());
return Ok(TradeList::new());
}

let mut transactions = Vec::new();
let mut current_transaction = String::new();
let mut trades = Vec::new();
let mut current_trade = String::new();
let mut bracket_depth = 0;

for c in content.chars() {
match c {
',' if bracket_depth == 0 => {
if !current_transaction.is_empty() {
let transaction = Transaction::from_str(&current_transaction)?;
transactions.push(transaction);
current_transaction.clear();
if !current_trade.is_empty() {
let trade = Trade::from_str(&current_trade)?;
trades.push(trade);
current_trade.clear();
}
}
'[' => {
bracket_depth += 1;
current_transaction.push(c);
current_trade.push(c);
}
']' => {
bracket_depth -= 1;
current_transaction.push(c);
current_trade.push(c);
}
_ => current_transaction.push(c),
_ => current_trade.push(c),
}
}

if !current_transaction.is_empty() {
let transaction = Transaction::from_str(&current_transaction)?;
transactions.push(transaction);
if !current_trade.is_empty() {
let trade = Trade::from_str(&current_trade)?;
trades.push(trade);
}

Ok(TransactionList { transactions })
Ok(TradeList { trades })
}
}

impl From<Vec<Transaction>> for TransactionList {
fn from(transactions: Vec<Transaction>) -> Self {
Self::from_vec(transactions)
impl From<Vec<Trade>> for TradeList {
fn from(trades: Vec<Trade>) -> Self {
Self::from_vec(trades)
}
}

impl From<TransactionList> for Vec<Transaction> {
fn from(list: TransactionList) -> Self {
impl From<TradeList> for Vec<Trade> {
fn from(list: TradeList) -> Self {
list.into_vec()
}
}
42 changes: 21 additions & 21 deletions src/execution/match_result.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::errors::PriceLevelError;
use crate::execution::list::TransactionList;
use crate::execution::transaction::Transaction;
use crate::execution::list::TradeList;
use crate::execution::transaction::Trade;
use crate::orders::OrderId;
use serde::{Deserialize, Serialize};
use std::fmt;
Expand All @@ -12,8 +12,8 @@ pub struct MatchResult {
/// The ID of the incoming order that initiated the match
pub order_id: OrderId,

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

/// Remaining quantity of the incoming order after matching
pub remaining_quantity: u64,
Expand All @@ -30,18 +30,18 @@ impl MatchResult {
pub fn new(order_id: OrderId, initial_quantity: u64) -> Self {
Self {
order_id,
transactions: TransactionList::new(),
trades: TradeList::new(),
remaining_quantity: initial_quantity,
is_complete: false,
filled_order_ids: Vec::new(),
}
}

/// Add a transaction to this match result
pub fn add_transaction(&mut self, transaction: Transaction) {
self.remaining_quantity = self.remaining_quantity.saturating_sub(transaction.quantity);
/// Add a trade to this match result
pub fn add_trade(&mut self, trade: Trade) {
self.remaining_quantity = self.remaining_quantity.saturating_sub(trade.quantity);
self.is_complete = self.remaining_quantity == 0;
self.transactions.add(transaction);
self.trades.add(trade);
}

/// Add a filled order ID to track orders removed from the book
Expand All @@ -51,12 +51,12 @@ impl MatchResult {

/// Get the total executed quantity
pub fn executed_quantity(&self) -> u64 {
self.transactions.as_vec().iter().map(|t| t.quantity).sum()
self.trades.as_vec().iter().map(|t| t.quantity).sum()
}

/// Get the total value executed
pub fn executed_value(&self) -> u128 {
self.transactions
self.trades
.as_vec()
.iter()
.map(|t| t.price * (t.quantity as u128))
Expand All @@ -81,7 +81,7 @@ impl fmt::Display for MatchResult {
"MatchResult:order_id={};remaining_quantity={};is_complete={}",
self.order_id, self.remaining_quantity, self.is_complete
)?;
write!(f, ";transactions={}", self.transactions)?;
write!(f, ";trades={}", self.trades)?;
write!(f, ";filled_order_ids=[")?;
for (i, order_id) in self.filled_order_ids.iter().enumerate() {
if i > 0 {
Expand Down Expand Up @@ -122,7 +122,7 @@ impl FromStr for MatchResult {
let mut order_id_str = None;
let mut remaining_quantity_str = None;
let mut is_complete_str = None;
let mut transactions_str = None;
let mut trades_str = None;
let mut filled_order_ids_str = None;

let mut pos = "MatchResult:".len();
Expand Down Expand Up @@ -151,13 +151,13 @@ impl FromStr for MatchResult {
is_complete_str = Some(value);
pos = next_pos;
}
"transactions" => {
if !s[pos..].starts_with("Transactions:[") {
"trades" => {
if !s[pos..].starts_with("Trades:[") {
return Err(PriceLevelError::InvalidFormat);
}

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

while i < s.len() && bracket_depth > 0 {
if s[i..].starts_with(']') {
Expand All @@ -178,7 +178,7 @@ impl FromStr for MatchResult {
return Err(PriceLevelError::InvalidFormat);
}

transactions_str = Some(&s[pos..=i]);
trades_str = Some(&s[pos..=i]);
pos = i + 1;
if pos < s.len() && s[pos..].starts_with(';') {
pos += 1;
Expand Down Expand Up @@ -230,8 +230,8 @@ impl FromStr for MatchResult {
.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 transactions_str = transactions_str
.ok_or_else(|| PriceLevelError::MissingField("transactions".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()))?;

Expand All @@ -256,7 +256,7 @@ impl FromStr for MatchResult {
value: is_complete_str.to_string(),
})?;

let transactions = TransactionList::from_str(transactions_str)?;
let trades = TradeList::from_str(trades_str)?;

let filled_order_ids = if filled_order_ids_str == "[]" {
Vec::new()
Expand All @@ -280,7 +280,7 @@ impl FromStr for MatchResult {

Ok(MatchResult {
order_id,
transactions,
trades,
remaining_quantity,
is_complete,
filled_order_ids,
Expand Down
3 changes: 2 additions & 1 deletion src/execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ mod list;
mod match_result;
mod tests;

pub use list::TradeList;
pub use match_result::MatchResult;
pub use transaction::Transaction;
pub use transaction::Trade;
Loading
Loading