use chrono::{DateTime, Utc};
use derive_more::Constructor;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
use std::fmt::{Display, Formatter};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, Constructor)]
pub struct AccountNotice<InstrumentKey> {
pub kind: NoticeKind,
pub instrument: Option<InstrumentKey>,
pub time_exchange: DateTime<Utc>,
pub margin_level: Option<Decimal>,
pub status: SmolStr,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum NoticeKind {
MarginCall,
LiquidationWarning,
Liquidation,
MarginRestored,
Other,
}
impl NoticeKind {
pub fn is_escalation(self) -> bool {
matches!(
self,
Self::MarginCall | Self::LiquidationWarning | Self::Liquidation
)
}
}
impl Display for NoticeKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::MarginCall => "margin call",
Self::LiquidationWarning => "liquidation warning",
Self::Liquidation => "liquidation",
Self::MarginRestored => "margin restored",
Self::Other => "other",
})
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
#[test]
fn only_rising_risk_is_an_escalation() {
for (kind, escalation) in [
(NoticeKind::MarginCall, true),
(NoticeKind::LiquidationWarning, true),
(NoticeKind::Liquidation, true),
(NoticeKind::MarginRestored, false),
(NoticeKind::Other, false),
] {
assert_eq!(kind.is_escalation(), escalation, "{kind}");
}
}
#[test]
fn a_notice_round_trips_through_serde() {
let notice = AccountNotice::new(
NoticeKind::LiquidationWarning,
Some(SmolStr::new("BTCUSDT")),
DateTime::from_timestamp_millis(1_700_000_000_000).unwrap(),
Some(Decimal::new(115, 2)),
SmolStr::new("PRE_LIQUIDATION"),
);
let json = serde_json::to_string(¬ice).unwrap();
assert_eq!(
serde_json::from_str::<AccountNotice<SmolStr>>(&json).unwrap(),
notice
);
}
}