use serde::{Deserialize, Deserializer};
use crate::utils::dec::Dec;
use crate::utils::ids::whole;
use crate::utils::order::known;
use crate::utils::{AccountIndex, ClientOrderIndex, MarketId, OrderIndex, OrderKind, OrderRef, Side, TimeInForce};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum OrderStatus {
InProgress,
Pending,
Open,
Filled,
Canceled,
CanceledPostOnly,
CanceledReduceOnly,
CanceledPositionNotAllowed,
CanceledMarginNotAllowed,
CanceledTooMuchSlippage,
CanceledNotEnoughLiquidity,
CanceledSelfTrade,
CanceledExpired,
CanceledOco,
CanceledChild,
CanceledLiquidation,
CanceledInvalidBalance,
#[serde(other)]
Other,
}
impl OrderStatus {
#[must_use]
pub const fn is_live(self) -> bool {
matches!(self, OrderStatus::InProgress | OrderStatus::Pending | OrderStatus::Open)
}
#[must_use]
pub const fn is_canceled(self) -> bool {
!self.is_live() && !matches!(self, OrderStatus::Filled | OrderStatus::Other)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TriggerStatus {
Na,
Ready,
MarkPrice,
Twap,
ParentOrder,
#[serde(other)]
Other,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Order {
#[serde(deserialize_with = "whole")]
pub order_index: u64,
#[serde(default, deserialize_with = "whole")]
pub client_order_index: u64,
pub market_index: MarketId,
pub owner_account_index: AccountIndex,
pub initial_base_amount: Dec,
pub remaining_base_amount: Dec,
#[serde(default)]
pub filled_base_amount: Option<Dec>,
#[serde(default)]
pub filled_quote_amount: Option<Dec>,
pub price: Dec,
#[serde(default, deserialize_with = "whole")]
pub base_size: u64,
#[serde(default, deserialize_with = "whole")]
pub base_price: u64,
#[serde(default, deserialize_with = "whole")]
pub nonce: u64,
pub is_ask: bool,
#[serde(rename = "type")]
pub kind: OrderKind,
#[serde(default, deserialize_with = "known")]
pub time_in_force: Option<TimeInForce>,
#[serde(default)]
pub reduce_only: bool,
#[serde(default, deserialize_with = "zero_if_empty")]
pub trigger_price: Dec,
#[serde(default, deserialize_with = "signed_ms")]
pub order_expiry: u64,
pub status: OrderStatus,
#[serde(default = "no_trigger")]
pub trigger_status: TriggerStatus,
#[serde(default, deserialize_with = "signed_ms")]
pub trigger_time: u64,
#[serde(default, deserialize_with = "whole")]
pub parent_order_index: u64,
#[serde(default, deserialize_with = "whole")]
pub block_height: u64,
#[serde(default, deserialize_with = "whole")]
pub timestamp: u64,
#[serde(default, deserialize_with = "whole")]
pub created_at: u64,
#[serde(default, deserialize_with = "whole")]
pub updated_at: u64,
#[serde(default, deserialize_with = "whole")]
pub transaction_time: u64,
#[serde(default, deserialize_with = "whole")]
pub order_version: u64,
#[serde(default)]
pub market_symbol: String,
}
const fn no_trigger() -> TriggerStatus {
TriggerStatus::Na
}
fn zero_if_empty<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Dec, D::Error> {
let text = String::deserialize(deserializer)?;
match text.trim().is_empty() {
true => Ok(Dec::ZERO),
false => Dec::parse(&text).map_err(serde::de::Error::custom),
}
}
fn signed_ms<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
let value = i64::deserialize(deserializer)?;
Ok(u64::try_from(value).unwrap_or(0))
}
impl Order {
#[must_use]
pub const fn side(&self) -> Side {
Side::from_is_ask(self.is_ask)
}
#[must_use]
pub fn index(&self) -> Option<OrderIndex> {
OrderIndex::new(self.order_index).ok()
}
#[must_use]
pub fn client_index(&self) -> Option<ClientOrderIndex> {
ClientOrderIndex::new(self.client_order_index).ok()
}
#[must_use]
pub fn reference(&self) -> Option<OrderRef> {
self.index().map(OrderRef::Venue)
}
#[must_use]
pub fn filled(&self) -> Dec {
self.filled_base_amount
.unwrap_or_else(|| self.initial_base_amount.checked_sub(self.remaining_base_amount).unwrap_or(Dec::ZERO))
}
}
#[cfg(test)]
#[path = "tests/orders.rs"]
mod tests;