use crate::errors::PriceLevelError;
use crate::execution::list::TradeList;
use crate::execution::trade::Trade;
use crate::orders::Id;
use crate::utils::Quantity;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum MatchOutcome {
Filled,
PartiallyFilled,
#[default]
NotFilled,
Killed,
Rejected,
}
impl MatchOutcome {
#[must_use]
#[inline]
pub fn was_killed(self) -> bool {
matches!(self, Self::Killed)
}
#[must_use]
#[inline]
pub fn was_rejected(self) -> bool {
matches!(self, Self::Rejected)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatchResult {
order_id: Id,
trades: TradeList,
remaining_quantity: u64,
is_complete: bool,
filled_order_ids: Vec<Id>,
#[serde(default)]
outcome: MatchOutcome,
}
impl MatchResult {
#[must_use]
pub fn new(order_id: Id, initial_quantity: Quantity) -> Self {
let is_complete = initial_quantity.as_u64() == 0;
Self {
order_id,
trades: TradeList::new(),
remaining_quantity: initial_quantity.as_u64(),
is_complete,
filled_order_ids: Vec::new(),
outcome: if is_complete {
MatchOutcome::Filled
} else {
MatchOutcome::NotFilled
},
}
}
#[must_use]
pub fn with_capacity(order_id: Id, initial_quantity: Quantity, capacity: usize) -> Self {
let is_complete = initial_quantity.as_u64() == 0;
Self {
order_id,
trades: TradeList::with_capacity(capacity),
remaining_quantity: initial_quantity.as_u64(),
is_complete,
filled_order_ids: Vec::with_capacity(capacity),
outcome: if is_complete {
MatchOutcome::Filled
} else {
MatchOutcome::NotFilled
},
}
}
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.outcome = if self.is_complete {
MatchOutcome::Filled
} else {
MatchOutcome::PartiallyFilled
};
self.trades.add(trade);
Ok(())
}
pub fn add_filled_order_id(&mut self, order_id: Id) {
self.filled_order_ids.push(order_id);
}
#[must_use]
pub fn order_id(&self) -> Id {
self.order_id
}
#[must_use]
pub fn trades(&self) -> &TradeList {
&self.trades
}
#[must_use]
pub fn remaining_quantity(&self) -> Quantity {
Quantity::new(self.remaining_quantity)
}
#[must_use]
pub fn is_complete(&self) -> bool {
self.is_complete
}
#[must_use]
pub fn filled_order_ids(&self) -> &[Id] {
&self.filled_order_ids
}
#[must_use]
pub fn outcome(&self) -> MatchOutcome {
self.outcome
}
#[must_use]
pub fn was_killed(&self) -> bool {
self.outcome.was_killed()
}
#[must_use]
pub fn was_rejected(&self) -> bool {
self.outcome.was_rejected()
}
pub(crate) fn finalize(&mut self, remaining_quantity: Quantity) {
self.remaining_quantity = remaining_quantity.as_u64();
self.is_complete = self.remaining_quantity == 0;
self.outcome = if self.is_complete {
MatchOutcome::Filled
} else if self.trades.is_empty() {
MatchOutcome::NotFilled
} else {
MatchOutcome::PartiallyFilled
};
}
pub(crate) fn mark_killed(&mut self, incoming_quantity: u64) {
self.trades = TradeList::new();
self.filled_order_ids.clear();
self.remaining_quantity = incoming_quantity;
self.is_complete = false;
self.outcome = MatchOutcome::Killed;
}
pub(crate) fn mark_rejected(&mut self, incoming_quantity: u64) {
self.trades = TradeList::new();
self.filled_order_ids.clear();
self.remaining_quantity = incoming_quantity;
self.is_complete = false;
self.outcome = MatchOutcome::Rejected;
}
pub fn executed_quantity(&self) -> Result<Quantity, 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(),
}
})
})
.map(Quantity::new)
}
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(),
})
})
}
pub fn average_price(&self) -> Result<Option<f64>, PriceLevelError> {
let executed_qty = self.executed_quantity()?.as_u64();
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>>()?
}
};
let outcome = if is_complete {
MatchOutcome::Filled
} else if trades.is_empty() {
MatchOutcome::NotFilled
} else {
MatchOutcome::PartiallyFilled
};
Ok(MatchResult {
order_id,
trades,
remaining_quantity,
is_complete,
filled_order_ids,
outcome,
})
}
}