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