use serde::{Deserialize, Deserializer, de};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Side {
Bid,
Ask,
}
impl Side {
#[must_use]
pub const fn from_is_ask(is_ask: bool) -> Side {
match is_ask {
true => Side::Ask,
false => Side::Bid,
}
}
#[must_use]
pub const fn is_ask(self) -> bool {
matches!(self, Side::Ask)
}
#[must_use]
pub const fn opposite(self) -> Side {
match self {
Side::Bid => Side::Ask,
Side::Ask => Side::Bid,
}
}
#[must_use]
pub const fn sign(self) -> i8 {
match self {
Side::Bid => 1,
Side::Ask => -1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OrderKind {
Limit,
Market,
StopLoss,
StopLossLimit,
TakeProfit,
TakeProfitLimit,
Twap,
TwapSub,
Liquidation,
}
impl OrderKind {
pub const ALL: [OrderKind; 9] = [
OrderKind::Limit,
OrderKind::Market,
OrderKind::StopLoss,
OrderKind::StopLossLimit,
OrderKind::TakeProfit,
OrderKind::TakeProfitLimit,
OrderKind::Twap,
OrderKind::TwapSub,
OrderKind::Liquidation,
];
#[must_use]
pub const fn code(self) -> u8 {
match self {
OrderKind::Limit => 0,
OrderKind::Market => 1,
OrderKind::StopLoss => 2,
OrderKind::StopLossLimit => 3,
OrderKind::TakeProfit => 4,
OrderKind::TakeProfitLimit => 5,
OrderKind::Twap => 6,
OrderKind::TwapSub => 7,
OrderKind::Liquidation => 8,
}
}
#[must_use]
pub fn from_code(code: u8) -> Option<OrderKind> {
OrderKind::ALL.into_iter().find(|kind| kind.code() == code)
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
OrderKind::Limit => "limit",
OrderKind::Market => "market",
OrderKind::StopLoss => "stop-loss",
OrderKind::StopLossLimit => "stop-loss-limit",
OrderKind::TakeProfit => "take-profit",
OrderKind::TakeProfitLimit => "take-profit-limit",
OrderKind::Twap => "twap",
OrderKind::TwapSub => "twap-sub",
OrderKind::Liquidation => "liquidation",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<OrderKind> {
OrderKind::ALL.into_iter().find(|kind| kind.name() == name)
}
#[must_use]
pub const fn is_placeable(self) -> bool {
self.code() <= OrderKind::Twap.code()
}
#[must_use]
pub const fn is_trigger(self) -> bool {
matches!(
self,
OrderKind::StopLoss | OrderKind::StopLossLimit | OrderKind::TakeProfit | OrderKind::TakeProfitLimit
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TimeInForce {
ImmediateOrCancel,
GoodTillTime,
PostOnly,
}
impl TimeInForce {
pub const ALL: [TimeInForce; 3] =
[TimeInForce::ImmediateOrCancel, TimeInForce::GoodTillTime, TimeInForce::PostOnly];
#[must_use]
pub const fn code(self) -> u8 {
match self {
TimeInForce::ImmediateOrCancel => 0,
TimeInForce::GoodTillTime => 1,
TimeInForce::PostOnly => 2,
}
}
#[must_use]
pub fn from_code(code: u8) -> Option<TimeInForce> {
TimeInForce::ALL.into_iter().find(|tif| tif.code() == code)
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
TimeInForce::ImmediateOrCancel => "immediate-or-cancel",
TimeInForce::GoodTillTime => "good-till-time",
TimeInForce::PostOnly => "post-only",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<TimeInForce> {
TimeInForce::ALL.into_iter().find(|tif| tif.name() == name)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum MarginMode {
#[default]
Cross,
Isolated,
}
impl MarginMode {
#[must_use]
pub const fn code(self) -> u8 {
match self {
MarginMode::Cross => 0,
MarginMode::Isolated => 1,
}
}
#[must_use]
pub const fn from_code(code: u8) -> Option<MarginMode> {
match code {
0 => Some(MarginMode::Cross),
1 => Some(MarginMode::Isolated),
_ => None,
}
}
}
impl<'de> Deserialize<'de> for MarginMode {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let code = u8::deserialize(deserializer)?;
MarginMode::from_code(code).ok_or_else(|| de::Error::custom(format!("{code} is not a margin mode")))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MarketKind {
Perps,
Spot,
BinaryOptions,
}
impl MarketKind {
pub const ALL: [MarketKind; 3] = [MarketKind::Perps, MarketKind::Spot, MarketKind::BinaryOptions];
#[must_use]
pub const fn code(self) -> u8 {
match self {
MarketKind::Perps => 0,
MarketKind::Spot => 1,
MarketKind::BinaryOptions => 2,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
MarketKind::Perps => "perps",
MarketKind::Spot => "spot",
MarketKind::BinaryOptions => "bo",
}
}
#[must_use]
pub const fn filter(self) -> &'static str {
match self {
MarketKind::Perps => "perp",
MarketKind::Spot => "spot",
MarketKind::BinaryOptions => "bo",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<MarketKind> {
match name {
"perps" | "perp" => Some(MarketKind::Perps),
"spot" => Some(MarketKind::Spot),
"bo" | "binary_options" | "binary-options" => Some(MarketKind::BinaryOptions),
_ => None,
}
}
}
pub(crate) trait Named: Sized {
fn from_name(name: &str) -> Option<Self>;
}
macro_rules! by_name {
($name:ident, $what:literal) => {
impl<'de> Deserialize<'de> for $name {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let name = String::deserialize(deserializer)?;
$name::from_name(&name).ok_or_else(|| de::Error::custom(format!("`{name}` is not {}", $what)))
}
}
impl Named for $name {
fn from_name(name: &str) -> Option<Self> {
$name::from_name(name)
}
}
};
}
pub(crate) fn known<'de, D: Deserializer<'de>, T: Named>(deserializer: D) -> Result<Option<T>, D::Error> {
let name = Option::<String>::deserialize(deserializer)?;
Ok(name.as_deref().and_then(T::from_name))
}
by_name!(OrderKind, "an order kind");
by_name!(TimeInForce, "a time in force");
by_name!(MarketKind, "a market kind");
#[cfg(test)]
#[path = "tests/order.rs"]
mod tests;