use std::num::NonZeroU16;
use alloy::primitives::U256;
use fastnum::UD64;
use thiserror::Error;
use super::{event, types};
use crate::{abi::dex, num};
#[derive(Debug, Clone, Error)]
pub enum OrderParseError {
#[error("order has invalid id 0")]
ZeroOrderId,
}
#[derive(Clone, Copy, derive_more::Debug)]
pub struct Order {
instant: types::StateInstant,
request_id: Option<types::RequestId>,
client_order_id: Option<types::RequestId>,
order_id: types::OrderId,
r#type: types::OrderType,
account_id: types::AccountId,
#[debug("{price}")]
price: UD64, #[debug("{size}")]
size: UD64, placed_size: Option<UD64>, expiry_block: u64,
#[debug("{leverage}")]
leverage: UD64,
post_only: Option<bool>,
fill_or_kill: Option<bool>,
immediate_or_cancel: Option<bool>,
builder: Option<types::BuilderAttribution>,
prev_order_id: Option<types::OrderId>,
next_order_id: Option<types::OrderId>,
}
impl Order {
pub(crate) fn from_snapshot(
instant: types::StateInstant,
order: dex::Exchange::OrderV2,
base_price: UD64,
price_converter: num::Converter,
size_converter: num::Converter,
leverage_converter: num::Converter,
) -> Result<Self, OrderParseError> {
let order_id = NonZeroU16::new(order.orderId).ok_or(OrderParseError::ZeroOrderId)?;
let prev_order_id = NonZeroU16::new(order.prevOrderId);
let next_order_id = NonZeroU16::new(order.nextOrderId);
Ok(Self {
instant,
request_id: None,
client_order_id: None, order_id,
r#type: order.orderType.into(),
account_id: order.accountId,
price: base_price + price_converter.from_unsigned(order.priceONS.to()),
size: size_converter.from_unsigned(order.lotLNS.to()),
placed_size: None,
expiry_block: order.expiryBlock as u64,
leverage: leverage_converter.from_u64(order.leverageHdths as u64),
post_only: None,
fill_or_kill: None,
immediate_or_cancel: None,
builder: (order.builderId != 0).then(|| {
types::BuilderAttribution::from_raw(
order.builderId,
U256::from(order.builderFeePer100K),
)
}),
prev_order_id,
next_order_id,
})
}
pub(crate) fn placed(
instant: types::StateInstant,
ctx: &event::OrderContext,
order_id: types::OrderId,
size: UD64,
price_converter: num::Converter,
leverage_converter: num::Converter,
) -> Self {
Self {
instant,
request_id: Some(ctx.request_id),
client_order_id: Some(ctx.request_id),
order_id,
r#type: ctx.r#type.into(),
account_id: ctx.account_id,
price: price_converter.from_unsigned(ctx.price),
size,
placed_size: Some(size),
expiry_block: ctx.expiry_block,
leverage: leverage_converter.from_unsigned(ctx.leverage),
post_only: Some(ctx.post_only),
fill_or_kill: Some(ctx.fill_or_kill),
immediate_or_cancel: Some(ctx.immediate_or_cancel),
builder: ctx.builder,
prev_order_id: None,
next_order_id: None,
}
}
pub(crate) fn updated(
&self,
instant: types::StateInstant,
ctx: &Option<event::OrderContext>,
price: Option<UD64>,
size: Option<UD64>,
placed_size: Option<UD64>,
expiry_block: Option<u64>,
) -> Self {
Self {
instant,
request_id: ctx.as_ref().map(|c| c.request_id),
client_order_id: self.client_order_id,
order_id: self.order_id,
r#type: self.r#type,
account_id: self.account_id,
price: price.unwrap_or(self.price),
size: size.unwrap_or(self.size),
placed_size: placed_size.or(self.placed_size),
expiry_block: expiry_block.unwrap_or(self.expiry_block),
leverage: self.leverage,
post_only: self.post_only,
fill_or_kill: self.fill_or_kill,
immediate_or_cancel: self.immediate_or_cancel,
builder: self.builder,
prev_order_id: self.prev_order_id,
next_order_id: self.next_order_id,
}
}
pub(crate) fn update_if_expired(&mut self, instant: types::StateInstant) -> bool {
if self.expiry_block != 0
&& self.expiry_block <= instant.block_number()
&& !self.is_expired()
{
self.instant = instant;
true
} else {
false
}
}
#[allow(unused)]
pub(crate) fn for_testing(r#type: types::OrderType, price: UD64, size: UD64) -> Self {
Self {
instant: types::StateInstant::new(0, 0),
request_id: None,
client_order_id: None,
order_id: NonZeroU16::MIN,
r#type,
account_id: 0,
price,
size,
placed_size: Some(size),
expiry_block: 0,
leverage: UD64::ZERO,
post_only: None,
fill_or_kill: None,
immediate_or_cancel: None,
builder: None,
prev_order_id: None,
next_order_id: None,
}
}
#[allow(unused)]
pub(crate) fn for_l3_testing(
r#type: types::OrderType,
price: UD64,
size: UD64,
block_number: u64,
order_id: types::OrderId,
account_id: types::AccountId,
) -> Self {
Self {
instant: types::StateInstant::new(block_number, 0),
request_id: None,
client_order_id: None,
order_id,
r#type,
account_id,
price,
size,
placed_size: Some(size),
expiry_block: 0,
leverage: UD64::ZERO,
post_only: None,
fill_or_kill: None,
immediate_or_cancel: None,
builder: None,
prev_order_id: None,
next_order_id: None,
}
}
#[allow(unused, clippy::too_many_arguments)]
pub(crate) fn for_l3_testing_with_links(
r#type: types::OrderType,
price: UD64,
size: UD64,
block_number: u64,
order_id: types::OrderId,
account_id: types::AccountId,
prev_order_id: Option<types::OrderId>,
next_order_id: Option<types::OrderId>,
) -> Self {
Self {
instant: types::StateInstant::new(block_number, 0),
request_id: None,
client_order_id: None,
order_id,
r#type,
account_id,
price,
size,
placed_size: Some(size),
expiry_block: 0,
leverage: UD64::ZERO,
post_only: None,
fill_or_kill: None,
immediate_or_cancel: None,
builder: None,
prev_order_id,
next_order_id,
}
}
#[allow(unused)]
pub(crate) fn with_size(&self, size: UD64) -> Self {
Self {
instant: self.instant,
request_id: self.request_id,
client_order_id: self.client_order_id,
order_id: self.order_id,
r#type: self.r#type,
account_id: self.account_id,
price: self.price,
size,
placed_size: self.placed_size,
expiry_block: self.expiry_block,
leverage: self.leverage,
post_only: self.post_only,
fill_or_kill: self.fill_or_kill,
immediate_or_cancel: self.immediate_or_cancel,
builder: self.builder,
prev_order_id: self.prev_order_id,
next_order_id: self.next_order_id,
}
}
#[allow(unused)]
pub(crate) fn with_price(&self, price: UD64) -> Self {
Self {
instant: self.instant,
request_id: self.request_id,
client_order_id: self.client_order_id,
order_id: self.order_id,
r#type: self.r#type,
account_id: self.account_id,
price,
size: self.size,
placed_size: self.placed_size,
expiry_block: self.expiry_block,
leverage: self.leverage,
post_only: self.post_only,
fill_or_kill: self.fill_or_kill,
immediate_or_cancel: self.immediate_or_cancel,
builder: self.builder,
prev_order_id: self.prev_order_id,
next_order_id: self.next_order_id,
}
}
#[allow(unused)]
pub(crate) fn with_expiry_block(&self, expiry_block: u64) -> Self {
Self {
instant: self.instant,
request_id: self.request_id,
client_order_id: self.client_order_id,
order_id: self.order_id,
r#type: self.r#type,
account_id: self.account_id,
price: self.price,
size: self.size,
placed_size: self.placed_size,
expiry_block,
leverage: self.leverage,
post_only: self.post_only,
fill_or_kill: self.fill_or_kill,
immediate_or_cancel: self.immediate_or_cancel,
builder: self.builder,
prev_order_id: self.prev_order_id,
next_order_id: self.next_order_id,
}
}
#[allow(unused)]
pub(crate) fn with_links(
&self,
prev_order_id: Option<types::OrderId>,
next_order_id: Option<types::OrderId>,
) -> Self {
Self {
instant: self.instant,
request_id: self.request_id,
client_order_id: self.client_order_id,
order_id: self.order_id,
r#type: self.r#type,
account_id: self.account_id,
price: self.price,
size: self.size,
placed_size: self.placed_size,
expiry_block: self.expiry_block,
leverage: self.leverage,
post_only: self.post_only,
fill_or_kill: self.fill_or_kill,
immediate_or_cancel: self.immediate_or_cancel,
builder: self.builder,
prev_order_id,
next_order_id,
}
}
#[allow(unused)]
pub(crate) fn with_client_order_id(&self, client_order_id: types::RequestId) -> Self {
Self {
instant: self.instant,
request_id: self.request_id,
client_order_id: Some(client_order_id),
order_id: self.order_id,
r#type: self.r#type,
account_id: self.account_id,
price: self.price,
size: self.size,
placed_size: self.placed_size,
expiry_block: self.expiry_block,
leverage: self.leverage,
post_only: self.post_only,
fill_or_kill: self.fill_or_kill,
immediate_or_cancel: self.immediate_or_cancel,
builder: self.builder,
prev_order_id: self.prev_order_id,
next_order_id: self.next_order_id,
}
}
pub fn instant(&self) -> types::StateInstant { self.instant }
pub fn request_id(&self) -> Option<types::RequestId> { self.request_id }
pub fn client_order_id(&self) -> Option<types::RequestId> { self.client_order_id }
pub fn order_id(&self) -> types::OrderId { self.order_id }
pub fn r#type(&self) -> types::OrderType { self.r#type }
pub fn account_id(&self) -> types::AccountId { self.account_id }
pub fn price(&self) -> UD64 { self.price }
pub fn size(&self) -> UD64 { self.size }
pub fn placed_size(&self) -> Option<UD64> { self.placed_size }
pub fn filled_size(&self) -> Option<UD64> {
self.placed_size.map(|placed_size| placed_size - self.size)
}
pub fn expiry_block(&self) -> u64 { self.expiry_block }
pub fn is_expired(&self) -> bool {
self.expiry_block != 0 && self.expiry_block <= self.instant.block_number()
}
pub fn leverage(&self) -> UD64 { self.leverage }
pub fn post_only(&self) -> Option<bool> { self.post_only }
pub fn fill_or_kill(&self) -> Option<bool> { self.fill_or_kill }
pub fn immediate_or_cancel(&self) -> Option<bool> { self.immediate_or_cancel }
pub fn builder(&self) -> Option<types::BuilderAttribution> { self.builder }
pub fn prev_order_id(&self) -> Option<types::OrderId> { self.prev_order_id }
pub fn next_order_id(&self) -> Option<types::OrderId> { self.next_order_id }
}
impl std::fmt::Display for Order {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
let mut short = format!(
"{} {:#} #{} 👤{}",
self.size(),
self.r#type(),
self.order_id(),
self.account_id(),
);
if self.expiry_block > 0 {
short.push_str(format!(" ⏳{}", self.expiry_block).as_str());
}
write!(f, "[{}]", short)
} else {
write!(
f,
"[{}@{} {:#} #{} acc:{} rq:{} exp:{}{} lev:{}]",
self.size(),
self.price(),
self.r#type(),
self.order_id(),
self.account_id(),
self.request_id().unwrap_or_default(),
self.expiry_block(),
if self.is_expired() { " (expired)" } else { "" },
self.leverage(),
)
}
}
}
#[cfg(feature = "display")]
impl tabled::Tabled for Order {
const LENGTH: usize = 13;
fn fields(&self) -> Vec<std::borrow::Cow<'_, str>> {
use colored::Colorize;
use crate::types::OrderSide;
vec![
match self.r#type.side() {
OrderSide::Ask => self.price().to_string().red().to_string().into(),
OrderSide::Bid => self.price().to_string().green().to_string().into(),
},
match self.r#type.side() {
OrderSide::Ask => self.size().to_string().red().to_string().into(),
OrderSide::Bid => self.size().to_string().green().to_string().into(),
},
match self.r#type.side() {
OrderSide::Ask => self.r#type().to_string().red().to_string().into(),
OrderSide::Bid => self.r#type().to_string().green().to_string().into(),
},
self.order_id().to_string().into(),
self.account_id().to_string().into(),
if let Some(request_id) = self.request_id() {
request_id.to_string().into()
} else {
"-".to_string().into()
},
if let Some(client_order_id) = self.client_order_id() {
client_order_id.to_string().into()
} else {
"-".to_string().into()
},
if self.expiry_block() > 0 {
if self.is_expired() {
(self.expiry_block().to_string() + " (expired)")
.bright_red()
.to_string()
.into()
} else {
self.expiry_block().to_string().into()
}
} else {
"-".to_string().into()
},
self.leverage().to_string().into(),
if self.post_only.unwrap_or_default() { "+" } else { "" }
.to_string()
.into(),
if self.fill_or_kill.unwrap_or_default() { "+" } else { "" }
.to_string()
.into(),
if self.immediate_or_cancel.unwrap_or_default() { "+" } else { "" }
.to_string()
.into(),
match self.builder {
Some(builder) => format!("{}@{}", builder.builder_id(), builder.fee()).into(),
None => "-".to_string().into(),
},
]
}
fn headers() -> Vec<std::borrow::Cow<'static, str>> {
vec![
"Price".into(),
"Size".into(),
"Type".into(),
"Order ID".into(),
"Account ID".into(),
"Request ID".into(),
"Client Order ID".into(),
"Expiry Block".into(),
"Leverage".into(),
"PO".into(),
"FoK".into(),
"IoC".into(),
"Builder".into(),
]
}
}