use std::{borrow::Cow, fmt, str::FromStr};
use serde::{Deserialize, Deserializer, de};
use crate::markets::side::Side;
use crate::utils::dec::Dec;
use crate::utils::hex::{self, BadHex};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Tif {
Alo,
Ioc,
Gtc,
FrontendMarket,
}
impl Tif {
#[must_use]
pub const fn wire(self) -> &'static str {
match self {
Tif::Alo => "Alo",
Tif::Ioc => "Ioc",
Tif::Gtc => "Gtc",
Tif::FrontendMarket => "FrontendMarket",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Tpsl {
TakeProfit,
StopLoss,
}
impl Tpsl {
#[must_use]
pub const fn wire(self) -> &'static str {
match self {
Tpsl::TakeProfit => "tp",
Tpsl::StopLoss => "sl",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Cloid([u8; 16]);
impl Cloid {
#[must_use]
pub const fn from_bytes(bytes: [u8; 16]) -> Cloid {
Cloid(bytes)
}
#[must_use]
pub const fn from_u128(value: u128) -> Cloid {
Cloid(value.to_be_bytes())
}
#[must_use]
pub const fn bytes(&self) -> &[u8; 16] {
&self.0
}
#[must_use]
pub fn to_hex(self) -> String {
hex::encode_prefixed(&self.0)
}
}
impl FromStr for Cloid {
type Err = BadHex;
fn from_str(text: &str) -> Result<Self, Self::Err> {
hex::decode_array(text).map(Cloid)
}
}
impl fmt::Display for Cloid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for Cloid {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
<Cow<'de, str>>::deserialize(deserializer)?.parse().map_err(de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenOrder {
pub coin: String,
pub side: Side,
pub limit_px: Dec,
pub sz: Dec,
pub oid: u64,
pub timestamp: u64,
pub orig_sz: Dec,
#[serde(default)]
pub reduce_only: bool,
#[serde(default)]
pub cloid: Option<Cloid>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OrderKind {
Limit,
Market,
StopMarket,
StopLimit,
TakeProfitMarket,
TakeProfitLimit,
Other(String),
}
impl OrderKind {
#[must_use]
pub fn parse(word: &str) -> OrderKind {
match word {
"Limit" => OrderKind::Limit,
"Market" => OrderKind::Market,
"Stop Market" => OrderKind::StopMarket,
"Stop Limit" => OrderKind::StopLimit,
"Take Profit Market" => OrderKind::TakeProfitMarket,
"Take Profit Limit" => OrderKind::TakeProfitLimit,
other => OrderKind::Other(other.to_owned()),
}
}
#[must_use]
pub fn word(&self) -> &str {
match self {
OrderKind::Limit => "Limit",
OrderKind::Market => "Market",
OrderKind::StopMarket => "Stop Market",
OrderKind::StopLimit => "Stop Limit",
OrderKind::TakeProfitMarket => "Take Profit Market",
OrderKind::TakeProfitLimit => "Take Profit Limit",
OrderKind::Other(word) => word,
}
}
#[must_use]
pub const fn tpsl(&self) -> Option<Tpsl> {
match self {
OrderKind::StopMarket | OrderKind::StopLimit => Some(Tpsl::StopLoss),
OrderKind::TakeProfitMarket | OrderKind::TakeProfitLimit => Some(Tpsl::TakeProfit),
_ => None,
}
}
#[must_use]
pub const fn fills_at_market(&self) -> bool {
matches!(self, OrderKind::Market | OrderKind::StopMarket | OrderKind::TakeProfitMarket)
}
}
impl<'de> Deserialize<'de> for OrderKind {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(OrderKind::parse(&<Cow<'de, str>>::deserialize(deserializer)?))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FrontendOrder {
pub coin: String,
pub side: Side,
pub limit_px: Dec,
pub sz: Dec,
pub oid: u64,
pub timestamp: u64,
pub trigger_condition: String,
pub is_trigger: bool,
pub trigger_px: Dec,
#[serde(default)]
pub children: Vec<FrontendOrder>,
pub is_position_tpsl: bool,
pub reduce_only: bool,
pub order_type: OrderKind,
pub orig_sz: Dec,
#[serde(default)]
pub tif: Option<String>,
#[serde(default)]
pub cloid: Option<Cloid>,
}
impl FrontendOrder {
#[must_use]
pub fn trigger(&self) -> Option<Dec> {
(self.is_trigger && self.trigger_px.is_positive()).then_some(self.trigger_px)
}
#[must_use]
pub const fn tpsl(&self) -> Option<Tpsl> {
self.order_type.tpsl()
}
#[must_use]
pub fn time_in_force(&self) -> Option<Tif> {
match self.tif.as_deref()? {
"Gtc" => Some(Tif::Gtc),
"Alo" => Some(Tif::Alo),
"Ioc" => Some(Tif::Ioc),
"FrontendMarket" => Some(Tif::FrontendMarket),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OrderState {
Open,
Filled,
Triggered,
Canceled(String),
Rejected(String),
Other(String),
}
impl OrderState {
#[must_use]
pub fn word(&self) -> &str {
match self {
OrderState::Open => "open",
OrderState::Filled => "filled",
OrderState::Triggered => "triggered",
OrderState::Canceled(word) | OrderState::Rejected(word) | OrderState::Other(word) => word,
}
}
}
impl<'de> Deserialize<'de> for OrderState {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let word = String::deserialize(deserializer)?;
Ok(match word.as_str() {
"open" => OrderState::Open,
"filled" => OrderState::Filled,
"triggered" => OrderState::Triggered,
"canceled" | "scheduledCancel" => OrderState::Canceled(word),
canceled if canceled.ends_with("Canceled") => OrderState::Canceled(word),
rejected if rejected == "rejected" || rejected.ends_with("Rejected") => OrderState::Rejected(word),
_ => OrderState::Other(word),
})
}
}
impl fmt::Display for OrderState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.word())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderRecord {
pub order: FrontendOrder,
pub status: OrderState,
pub status_timestamp: u64,
}