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)]
#[serde(try_from = "MatchResultWire")]
pub struct MatchResult {
order_id: Id,
trades: TradeList,
remaining_quantity: u64,
is_complete: bool,
filled_order_ids: Vec<Id>,
#[serde(serialize_with = "serialize_outcome_as_some")]
outcome: MatchOutcome,
}
fn serialize_outcome_as_some<S>(outcome: &MatchOutcome, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_some(outcome)
}
#[derive(Deserialize)]
struct MatchResultWire {
order_id: Id,
trades: TradeList,
remaining_quantity: u64,
is_complete: bool,
filled_order_ids: Vec<Id>,
#[serde(default)]
outcome: Option<MatchOutcome>,
}
impl TryFrom<MatchResultWire> for MatchResult {
type Error = PriceLevelError;
fn try_from(wire: MatchResultWire) -> Result<Self, Self::Error> {
let outcome = wire.outcome.unwrap_or({
if wire.remaining_quantity == 0 {
MatchOutcome::Filled
} else if wire.trades.is_empty() {
MatchOutcome::NotFilled
} else {
MatchOutcome::PartiallyFilled
}
});
MatchResult {
order_id: wire.order_id,
trades: wire.trades,
remaining_quantity: wire.remaining_quantity,
is_complete: wire.is_complete,
filled_order_ids: wire.filled_order_ids,
outcome,
}
.validated()
}
}
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> {
if trade.taker_order_id() != self.order_id {
return Err(PriceLevelError::InvalidOperation {
message: format!(
"trade taker order id {} does not match the result's incoming order id {}",
trade.taker_order_id(),
self.order_id
),
});
}
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))
}
}
fn validated(self) -> Result<Self, PriceLevelError> {
if self.is_complete != (self.remaining_quantity == 0) {
return Err(PriceLevelError::InvalidOperation {
message: format!(
"is_complete ({}) disagrees with remaining_quantity ({})",
self.is_complete, self.remaining_quantity
),
});
}
let executed = self.executed_quantity()?.as_u64();
if executed.checked_add(self.remaining_quantity).is_none() {
return Err(PriceLevelError::InvalidOperation {
message: format!(
"executed quantity ({executed}) plus remaining_quantity ({}) overflows u64",
self.remaining_quantity
),
});
}
let outcome_consistent = match self.outcome {
MatchOutcome::Filled => self.remaining_quantity == 0,
MatchOutcome::PartiallyFilled => self.remaining_quantity > 0 && !self.trades.is_empty(),
MatchOutcome::NotFilled => self.remaining_quantity > 0 && self.trades.is_empty(),
MatchOutcome::Killed | MatchOutcome::Rejected => {
self.remaining_quantity > 0
&& self.trades.is_empty()
&& self.filled_order_ids.is_empty()
}
};
if !outcome_consistent {
return Err(PriceLevelError::InvalidOperation {
message: format!(
"{:?} outcome contradicts the decoded fields \
(remaining_quantity {}, {} trade(s), {} filled id(s))",
self.outcome,
self.remaining_quantity,
self.trades.len(),
self.filled_order_ids.len()
),
});
}
if let Some(alien) = self
.trades
.as_vec()
.iter()
.find(|trade| trade.taker_order_id() != self.order_id)
{
return Err(PriceLevelError::InvalidOperation {
message: format!(
"trade taker order id {} does not match the result's incoming order id {}",
alien.taker_order_id(),
self.order_id
),
});
}
if !self.filled_order_ids.is_empty() {
let mut seen = std::collections::HashSet::with_capacity(self.filled_order_ids.len());
let mut makers = self
.trades
.as_vec()
.iter()
.map(|trade| trade.maker_order_id());
for filled in &self.filled_order_ids {
if !seen.insert(*filled) {
return Err(PriceLevelError::InvalidOperation {
message: format!("filled order id {filled} appears more than once"),
});
}
if !makers.by_ref().any(|maker| maker == *filled) {
return Err(PriceLevelError::InvalidOperation {
message: format!(
"filled order id {filled} is not an in-order maker of the trades"
),
});
}
}
}
Ok(self)
}
}
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 bytes = s.as_bytes();
let mut pos = start_pos;
while let Some(&byte) = bytes.get(pos) {
if byte == b';' {
let value = s
.get(start_pos..pos)
.ok_or(PriceLevelError::InvalidFormat)?;
return Ok((value, pos + 1));
}
pos += 1;
}
let value = s.get(start_pos..).ok_or(PriceLevelError::InvalidFormat)?;
Ok((value, bytes.len()))
}
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 bytes = s.as_bytes();
let mut pos = "MatchResult:".len();
while pos < bytes.len() {
let field_end = match bytes
.get(pos..)
.and_then(|rest| rest.iter().position(|&b| b == b'='))
{
Some(idx) => pos + idx,
None => return Err(PriceLevelError::InvalidFormat),
};
let field_name = s
.get(pos..field_end)
.ok_or(PriceLevelError::InvalidFormat)?;
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 !bytes
.get(pos..)
.is_some_and(|rest| rest.starts_with(b"Trades:["))
{
return Err(PriceLevelError::InvalidFormat);
}
let mut bracket_depth = 1;
let mut i = pos + "Trades:[".len();
while bracket_depth > 0 {
match bytes.get(i) {
Some(b']') => {
bracket_depth -= 1;
if bracket_depth == 0 {
break;
}
i += 1;
}
Some(b'[') => {
bracket_depth += 1;
i += 1;
}
Some(_) => {
i += 1;
}
None => break,
}
}
if bracket_depth > 0 {
return Err(PriceLevelError::InvalidFormat);
}
trades_str = Some(s.get(pos..=i).ok_or(PriceLevelError::InvalidFormat)?);
pos = i + 1;
if bytes.get(pos) == Some(&b';') {
pos += 1;
} else if pos < bytes.len() {
return Err(PriceLevelError::InvalidFormat);
}
}
"filled_order_ids" => {
if bytes.get(pos) != Some(&b'[') {
return Err(PriceLevelError::InvalidFormat);
}
let mut bracket_depth = 1;
let mut i = pos + 1;
while bracket_depth > 0 {
match bytes.get(i) {
Some(b']') => {
bracket_depth -= 1;
if bracket_depth == 0 {
break;
}
i += 1;
}
Some(b'[') => {
bracket_depth += 1;
i += 1;
}
Some(_) => {
i += 1;
}
None => break,
}
}
if bracket_depth > 0 {
return Err(PriceLevelError::InvalidFormat);
}
filled_order_ids_str =
Some(s.get(pos..=i).ok_or(PriceLevelError::InvalidFormat)?);
pos = i + 1;
if bytes.get(pos) == Some(&b';') {
pos += 1;
} else if pos < bytes.len() {
return Err(PriceLevelError::InvalidFormat);
}
}
_ => 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
};
MatchResult {
order_id,
trades,
remaining_quantity,
is_complete,
filled_order_ids,
outcome,
}
.validated()
}
}