Skip to main content

rustrade_data/exchange/binance/futures/
liquidation.rs

1use super::super::BinanceChannel;
2use crate::{
3    Identifier,
4    event::{MarketEvent, MarketIter},
5    subscription::liquidation::Liquidation,
6};
7use chrono::{DateTime, Utc};
8use rust_decimal::Decimal;
9use rustrade_instrument::{Side, exchange::ExchangeId};
10use rustrade_integration::subscription::SubscriptionId;
11use serde::{Deserialize, Serialize};
12
13/// [`BinanceFuturesUsd`](super::BinanceFuturesUsd) Liquidation order message.
14///
15/// ### Raw Payload Examples
16/// See docs: <https://binance-docs.github.io/apidocs/futures/en/#liquidation-order-streams>
17/// ```json
18/// {
19///     "e": "forceOrder",
20///     "E": 1665523974222,
21///     "o": {
22///         "s": "BTCUSDT",
23///         "S": "SELL",
24///         "o": "LIMIT",
25///         "f": "IOC",
26///         "q": "0.009",
27///         "p": "18917.15",
28///         "ap": "18990.00",
29///         "X": "FILLED",
30///         "l": "0.009",
31///         "z": "0.009",
32///         "T": 1665523974217
33///     }
34/// }
35/// ```
36#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
37pub struct BinanceLiquidation {
38    #[serde(alias = "o")]
39    pub order: BinanceLiquidationOrder,
40}
41
42/// [`BinanceFuturesUsd`](super::BinanceFuturesUsd) Liquidation order.
43///
44/// ### Raw Payload Examples
45/// ```json
46/// {
47///     "s": "BTCUSDT",
48///     "S": "SELL",
49///     "o": "LIMIT",
50///     "f": "IOC",
51///     "q": "0.009",
52///     "p": "18917.15",
53///     "ap": "18990.00",
54///     "X": "FILLED",
55///     "l": "0.009",
56///     "z": "0.009",
57///     "T": 1665523974217
58/// }
59/// ```
60///
61/// See docs: <https://binance-docs.github.io/apidocs/futures/en/#liquidation-order-streams>
62#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
63pub struct BinanceLiquidationOrder {
64    #[serde(alias = "s", deserialize_with = "de_liquidation_subscription_id")]
65    pub subscription_id: SubscriptionId,
66    #[serde(alias = "S")]
67    pub side: Side,
68    #[serde(
69        alias = "p",
70        deserialize_with = "rustrade_integration::serde::de::de_str"
71    )]
72    pub price: Decimal,
73    #[serde(
74        alias = "q",
75        deserialize_with = "rustrade_integration::serde::de::de_str"
76    )]
77    pub quantity: Decimal,
78    #[serde(
79        alias = "T",
80        deserialize_with = "rustrade_integration::serde::de::de_u64_epoch_ms_as_datetime_utc"
81    )]
82    pub time: DateTime<Utc>,
83}
84
85impl Identifier<Option<SubscriptionId>> for BinanceLiquidation {
86    fn id(&self) -> Option<SubscriptionId> {
87        Some(self.order.subscription_id.clone())
88    }
89}
90
91impl<InstrumentKey> From<(ExchangeId, InstrumentKey, BinanceLiquidation)>
92    for MarketIter<InstrumentKey, Liquidation>
93{
94    fn from(
95        (exchange_id, instrument, liquidation): (ExchangeId, InstrumentKey, BinanceLiquidation),
96    ) -> Self {
97        Self(vec![Ok(MarketEvent {
98            time_exchange: liquidation.order.time,
99            time_received: Utc::now(),
100            exchange: exchange_id,
101            instrument,
102            kind: Liquidation {
103                side: liquidation.order.side,
104                price: liquidation.order.price,
105                quantity: liquidation.order.quantity,
106                time: liquidation.order.time,
107            },
108        })])
109    }
110}
111
112/// Deserialize a [`BinanceLiquidationOrder`] "s" (eg/ "BTCUSDT") as the associated
113/// [`SubscriptionId`].
114///
115/// eg/ "forceOrder|BTCUSDT"
116pub fn de_liquidation_subscription_id<'de, D>(deserializer: D) -> Result<SubscriptionId, D::Error>
117where
118    D: serde::de::Deserializer<'de>,
119{
120    Deserialize::deserialize(deserializer).map(|market: String| {
121        SubscriptionId::from(format!("{}|{}", BinanceChannel::LIQUIDATIONS.0, market))
122    })
123}
124
125#[cfg(test)]
126#[allow(clippy::unwrap_used)] // Test code: panics on bad input are acceptable
127mod tests {
128    use super::*;
129
130    mod de {
131        use super::*;
132        use rust_decimal_macros::dec;
133        use rustrade_integration::serde::de::datetime_utc_from_epoch_duration;
134        use std::time::Duration;
135
136        #[test]
137        fn test_binance_liquidation() {
138            let input = r#"
139            {
140                "e": "forceOrder",
141                "E": 1665523974222,
142                "o": {
143                    "s": "BTCUSDT",
144                    "S": "SELL",
145                    "o": "LIMIT",
146                    "f": "IOC",
147                    "q": "0.009",
148                    "p": "18917.15",
149                    "ap": "18990.00",
150                    "X": "FILLED",
151                    "l": "0.009",
152                    "z": "0.009",
153                    "T": 1665523974217
154                }
155            }
156            "#;
157
158            assert_eq!(
159                serde_json::from_str::<BinanceLiquidation>(input).unwrap(),
160                BinanceLiquidation {
161                    order: BinanceLiquidationOrder {
162                        subscription_id: SubscriptionId::from("@forceOrder|BTCUSDT"),
163                        side: Side::Sell,
164                        price: dec!(18917.15),
165                        quantity: dec!(0.009),
166                        time: datetime_utc_from_epoch_duration(Duration::from_millis(
167                            1665523974217,
168                        )),
169                    },
170                }
171            );
172        }
173    }
174}