use std::fmt::{self, Display};
use derive_getters::Getters;
#[cfg(test)]
use derive_setters::Setters;
use rust_decimal::Decimal;
use strum_macros::Display;
use crate::{core::CoreResult, trade_log};
#[derive(Debug, thiserror::Error)]
#[error("Order error: {0}")]
pub struct OrderError(pub String);
impl OrderError {
pub fn new(msg: &str) -> Self {
OrderError(msg.to_string())
}
}
#[derive(
Debug, Clone, Copy, Display, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash,
)]
#[serde(rename_all = "PascalCase")]
pub enum Crypto {
BTC,
DOGE,
ETH,
SOL,
USDC,
USDT,
XEL,
}
#[derive(Debug, Clone, Copy, Display, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub enum OrderStatus {
Open,
PartiallyFilled,
PartiallyCanceled,
Filled,
Canceled,
Tested,
Failed,
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
pub enum OrderType {
Limit,
Market,
LimitMaker,
ImmediateOrCancel,
FillOrKill,
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
pub enum OrderSide {
Buy,
Sell,
}
pub trait SpecificOrderDetails:
Clone
+ Default
+ fmt::Debug
+ Display
+ serde::Serialize
+ PartialEq
+ 'static
+ Send
+ Sync
+ for<'de> serde::Deserialize<'de>
{
const NAME: &'static str;
fn try_update(&mut self, item: Self) -> CoreResult<()>;
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct EmptySpecificOrderDetails {
content: Option<()>,
}
impl SpecificOrderDetails for EmptySpecificOrderDetails {
const NAME: &'static str = "EmptySpecificOrderDetails";
fn try_update(&mut self, _: Self) -> CoreResult<()> {
Ok(())
}
}
impl Display for EmptySpecificOrderDetails {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<EmptySpecificOrderDetails>")
}
}
pub const DEFAULT_EMPTY_DETAILS: EmptySpecificOrderDetails =
EmptySpecificOrderDetails { content: None };
#[derive(Debug, Clone, Getters, serde::Serialize, serde::Deserialize, PartialEq)]
#[cfg_attr(test, derive(Setters))]
#[cfg_attr(test, setters(prefix = "with_"))]
#[serde(bound = "D: SpecificOrderDetails")]
pub struct Order<D: SpecificOrderDetails> {
#[getter(skip)]
#[cfg_attr(test, setters(skip))]
_id: String,
asset: Crypto,
quote: Crypto,
side: OrderSide,
order_type: OrderType,
status: OrderStatus,
qty_asset: Option<Decimal>,
qty_quote: Option<Decimal>,
price: Option<Decimal>,
executed_qty_asset: Decimal,
executed_qty_quote: Decimal,
details: D,
}
impl<D: SpecificOrderDetails> Order<D> {
pub fn new(
asset: Crypto,
quote: Crypto,
side: OrderSide,
order_type: OrderType,
qty_asset: Option<Decimal>,
qty_quote: Option<Decimal>,
price: Option<Decimal>,
) -> Result<Self, OrderError> {
match order_type {
OrderType::Limit => {
if price.is_none() {
return Err(OrderError::new("Limit order should have a price"));
}
}
OrderType::Market => {
if price.is_some() {
return Err(OrderError::new("Market order should not have a price"));
}
}
OrderType::LimitMaker | OrderType::ImmediateOrCancel | OrderType::FillOrKill => {
return Err(OrderError::new("Unimplemented order type"));
}
}
tracing::trace!(
"Creating order: asset: {}, quote: {}, side: {:?}, type: {:?}, quantity_asset: {:?}, quantity_quote: {:?}, price: {:?}",
asset,
quote,
side,
order_type,
qty_asset,
qty_quote,
price
);
Ok(Self {
asset,
quote,
side,
order_type,
qty_asset,
qty_quote,
price,
status: OrderStatus::Open,
_id: uuid::Uuid::new_v4().to_string(),
executed_qty_asset: Decimal::from(0),
executed_qty_quote: Decimal::from(0),
details: D::default(),
})
}
pub fn id(&self) -> &String {
&self._id
}
pub fn update(&mut self, update: UpdateOrderOutput<D>) -> CoreResult<bool> {
if self.status == update.status
&& self.executed_qty_asset == update.executed_qty_asset
&& self.executed_qty_quote == update.executed_qty_quote
&& self.details == update.details
{
return Ok(false);
}
self.status = update.status;
self.executed_qty_asset = update.executed_qty_asset;
self.executed_qty_quote = update.executed_qty_quote;
self.details.try_update(update.details)?;
trade_log!(
"Order updated: id: {}, status: {:?}, executed_asset: {}, executed_quote: {}, details: {}",
self.id(),
self.status,
self.executed_qty_asset,
self.executed_qty_quote,
self.details
);
Ok(true)
}
pub fn is_done(&self) -> bool {
matches!(
self.status,
OrderStatus::Filled
| OrderStatus::Canceled
| OrderStatus::PartiallyCanceled
| OrderStatus::Tested
| OrderStatus::Failed
)
}
pub fn symbol(&self) -> String {
format!("{}{}", self.asset(), self.quote())
}
}
#[derive(Debug, Clone)]
pub struct OrderDraft {
pub asset: Crypto,
pub quote: Crypto,
pub side: OrderSide,
pub order_type: OrderType,
pub qty_asset: Option<Decimal>,
pub qty_quote: Option<Decimal>,
pub price: Option<Decimal>,
}
#[derive(Debug, Clone)]
pub struct UpdateOrderOutput<OrderDetails: SpecificOrderDetails> {
pub status: OrderStatus,
pub executed_qty_asset: Decimal,
pub executed_qty_quote: Decimal,
pub details: OrderDetails,
}
impl<D: SpecificOrderDetails> From<&Order<D>> for UpdateOrderOutput<D> {
fn from(order: &Order<D>) -> Self {
Self {
status: order.status,
executed_qty_asset: order.executed_qty_asset,
executed_qty_quote: order.executed_qty_quote,
details: order.details.clone(),
}
}
}