Skip to main content

rustrade_data/exchange/gateio/perpetual/
trade.rs

1use super::super::message::GateioMessage;
2use crate::{
3    Identifier,
4    event::{MarketEvent, MarketIter},
5    exchange::ExchangeSub,
6    subscription::trade::PublicTrade,
7};
8use chrono::{DateTime, Utc};
9use rust_decimal::Decimal;
10use rustrade_instrument::{Side, exchange::ExchangeId};
11use rustrade_integration::subscription::SubscriptionId;
12use serde::{Deserialize, Deserializer, Serialize};
13use smol_str::format_smolstr;
14
15/// Deserialize a signed integer (i64) as Decimal.
16/// Gate.io Futures sends `size` as a bare JSON integer (e.g., -108 for sells).
17fn de_i64_as_decimal<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
18where
19    D: Deserializer<'de>,
20{
21    i64::deserialize(deserializer).map(Decimal::from)
22}
23
24/// Terse type alias for a `GateioFuturesUsdt`, `GateioFuturesBtc`, `GateioPerpetualUsdt` and
25/// `GateioPerpetualBtc` real-time trades WebSocket message.
26pub type GateioFuturesTrades = GateioMessage<Vec<GateioFuturesTradeInner>>;
27
28/// `GateioFuturesUsdt`, `GateioFuturesBtc`, `GateioPerpetualUsdt` and `GateioPerpetualBtc`
29/// real-time trade WebSocket message.
30///
31/// ### Raw Payload Examples
32/// #### Future Sell Trade
33/// See docs: <https://www.gate.io/docs/developers/delivery/ws/en/#trades-notification>
34/// ```json
35/// {
36///   "id": 27753479,
37///   "create_time": 1545136464,
38///   "create_time_ms": 1545136464123,
39///   "price": "96.4",
40///   "size": -108,
41///   "contract": "ETH_USDT_QUARTERLY_20201225"
42/// }
43/// ```
44///
45/// #### Future Perpetual Sell Trade
46/// See docs: <https://www.gate.io/docs/developers/futures/ws/en/#trades-api>
47/// ```json
48/// {
49///   "id": 27753479,
50///   "create_time": 1545136464,
51///   "create_time_ms": 1545136464123,
52///   "price": "96.4",
53///   "size": -108,
54///   "contract": "BTC_USD"
55/// }
56/// ```
57#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
58pub struct GateioFuturesTradeInner {
59    #[serde(rename = "contract")]
60    pub market: String,
61    #[serde(
62        rename = "create_time_ms",
63        deserialize_with = "rustrade_integration::serde::de::de_u64_epoch_ms_as_datetime_utc"
64    )]
65    pub time: DateTime<Utc>,
66    pub id: u64,
67    #[serde(deserialize_with = "rustrade_integration::serde::de::de_str")]
68    pub price: Decimal,
69    #[serde(rename = "size", deserialize_with = "de_i64_as_decimal")]
70    pub amount: Decimal,
71}
72
73impl Identifier<Option<SubscriptionId>> for GateioFuturesTrades {
74    fn id(&self) -> Option<SubscriptionId> {
75        self.data
76            .first()
77            .map(|trade| ExchangeSub::from((&self.channel, &trade.market)).id())
78    }
79}
80
81impl<InstrumentKey: Clone> From<(ExchangeId, InstrumentKey, GateioFuturesTrades)>
82    for MarketIter<InstrumentKey, PublicTrade>
83{
84    fn from(
85        (exchange, instrument, trades): (ExchangeId, InstrumentKey, GateioFuturesTrades),
86    ) -> Self {
87        trades
88            .data
89            .into_iter()
90            .map(|trade| {
91                Ok(MarketEvent {
92                    time_exchange: trade.time,
93                    time_received: Utc::now(),
94                    exchange,
95                    instrument: instrument.clone(),
96                    kind: PublicTrade {
97                        id: format_smolstr!("{}", trade.id),
98                        price: trade.price,
99                        amount: trade.amount.abs(),
100                        side: Some(if trade.amount.is_sign_positive() {
101                            Side::Buy
102                        } else {
103                            Side::Sell
104                        }),
105                    },
106                })
107            })
108            .collect()
109    }
110}
111
112#[cfg(test)]
113#[allow(clippy::unwrap_used)] // Test code: panics on bad input are acceptable
114mod tests {
115    use super::*;
116
117    mod de {
118        use super::*;
119
120        #[test]
121        fn test_gateio_message_perpetual_trade() {
122            let input = "{\"time\":1669843487,\"time_ms\":1669843487733,\"channel\":\"perpetual.trades\",\"event\":\"update\",\"result\":[{\"contract\":\"ETH_USDT\",\"create_time\":1669843487,\"create_time_ms\":1669843487724,\"id\":180276616,\"price\":\"1287\",\"size\":3}]}";
123            serde_json::from_str::<GateioFuturesTrades>(input).unwrap();
124        }
125
126        #[test]
127        fn test_gateio_message_futures_trade() {
128            let input = r#"
129            {
130              "channel": "futures.trades",
131              "event": "update",
132              "time": 1541503698,
133              "result": [
134                {
135                  "size": -108,
136                  "id": 27753479,
137                  "create_time": 1545136464,
138                  "create_time_ms": 1545136464123,
139                  "price": "96.4",
140                  "contract": "ETH_USDT_QUARTERLY_20201225"
141                }
142              ]
143            }"#;
144
145            serde_json::from_str::<GateioFuturesTrades>(input).unwrap();
146        }
147    }
148}