Skip to main content

rustrade_data/exchange/bitfinex/
trade.rs

1use crate::{
2    event::{MarketEvent, MarketIter},
3    subscription::trade::PublicTrade,
4};
5use chrono::{DateTime, Utc};
6use rustrade_instrument::{Side, exchange::ExchangeId};
7use rustrade_integration::serde::de::{datetime_utc_from_epoch_duration, extract_next};
8use serde::Serialize;
9use smol_str::format_smolstr;
10
11/// [`Bitfinex`](super::Bitfinex) real-time trade message.
12///
13/// ### Raw Payload Examples
14/// Format: \[ID, TIME, AMOUNT, PRICE\], <br> where +/- of amount indicates Side
15///
16/// See docs: <https://docs.bitfinex.com/reference/ws-public-trades>
17///
18/// #### Side::Buy Trade
19/// See docs: <https://docs.bitfinex.com/reference/ws-public-trades>
20/// ```json
21/// [420191,"te",[1225484398,1665452200022,0.08980641,19027.02807752]]
22/// ```
23///
24/// #### Side::Sell Trade
25/// See docs: <https://docs.bitfinex.com/reference/ws-public-trades>
26/// ```json
27/// [420191,"te",[1225484398,1665452200022,-0.08980641,19027.02807752]]
28/// ```
29///
30/// ## Notes:
31/// - [`Bitfinex`](super::Bitfinex) trades subscriptions results in receiving tag="te" & tag="tu"
32///   trades, both of which are identical.
33/// - "te" trades arrive marginally faster.
34/// - Therefore, tag="tu" trades are filtered out and considered only as additional Heartbeats.
35///
36/// See docs: <https://docs.bitfinex.com/reference/ws-public-trades>
37#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Serialize)]
38pub struct BitfinexTrade {
39    pub id: u64,
40    pub time: DateTime<Utc>,
41    pub side: Side,
42    pub price: f64,
43    pub amount: f64,
44}
45
46impl<InstrumentKey> From<(ExchangeId, InstrumentKey, BitfinexTrade)>
47    for MarketIter<InstrumentKey, PublicTrade>
48{
49    fn from((exchange_id, instrument, trade): (ExchangeId, InstrumentKey, BitfinexTrade)) -> Self {
50        Self(vec![Ok(MarketEvent {
51            time_exchange: trade.time,
52            time_received: Utc::now(),
53            exchange: exchange_id,
54            instrument,
55            kind: PublicTrade {
56                id: format_smolstr!("{}", trade.id),
57                price: trade.price,
58                amount: trade.amount,
59                side: trade.side,
60            },
61        })])
62    }
63}
64
65impl<'de> serde::Deserialize<'de> for BitfinexTrade {
66    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67    where
68        D: serde::de::Deserializer<'de>,
69    {
70        struct SeqVisitor;
71
72        impl<'de> serde::de::Visitor<'de> for SeqVisitor {
73            type Value = BitfinexTrade;
74
75            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76                formatter.write_str("BitfinexTrade struct from the Bitfinex WebSocket API")
77            }
78
79            fn visit_seq<SeqAccessor>(
80                self,
81                mut seq: SeqAccessor,
82            ) -> Result<Self::Value, SeqAccessor::Error>
83            where
84                SeqAccessor: serde::de::SeqAccess<'de>,
85            {
86                // Trade: [ID, TIME, AMOUNT,PRICE]
87                let id = extract_next(&mut seq, "id")?;
88                let time_millis = extract_next(&mut seq, "time")?;
89                let amount: f64 = extract_next(&mut seq, "amount")?;
90                let price = extract_next(&mut seq, "price")?;
91                let side = match amount.is_sign_positive() {
92                    true => Side::Buy,
93                    false => Side::Sell,
94                };
95
96                // Ignore any additional elements or SerDe will fail
97                //  '--> Bitfinex may add fields without warning
98                while seq.next_element::<serde::de::IgnoredAny>()?.is_some() {}
99
100                Ok(BitfinexTrade {
101                    id,
102                    time: datetime_utc_from_epoch_duration(std::time::Duration::from_millis(
103                        time_millis,
104                    )),
105                    price,
106                    amount: amount.abs(),
107                    side,
108                })
109            }
110        }
111
112        // Use Visitor implementation to deserialise the BitfinexTrade message
113        deserializer.deserialize_seq(SeqVisitor)
114    }
115}