1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use crate::{
    event::{MarketEvent, MarketIter},
    exchange::ExchangeId,
    subscription::trade::PublicTrade,
};
use barter_integration::{
    de::{datetime_utc_from_epoch_duration, extract_next},
    model::{Exchange, Instrument, Side},
};
use chrono::{DateTime, Utc};
use serde::Serialize;

/// [`Bitfinex`](super::Bitfinex) real-time trade message.
///
/// ### Raw Payload Examples
/// Format: \[ID, TIME, AMOUNT, PRICE\], <br> where +/- of amount indicates Side
///
/// See docs: <https://docs.bitfinex.com/reference/ws-public-trades>
///
/// #### Side::Buy Trade
/// See docs: <https://docs.bitfinex.com/reference/ws-public-trades>
/// ```json
/// [420191,"te",[1225484398,1665452200022,0.08980641,19027.02807752]]
/// ```
///
/// #### Side::Sell Trade
/// See docs: <https://docs.bitfinex.com/reference/ws-public-trades>
/// ```json
/// [420191,"te",[1225484398,1665452200022,-0.08980641,19027.02807752]]
/// ```
///
/// ## Notes:
/// - [`Bitfinex`](super::Bitfinex) trades subscriptions results in receiving tag="te" & tag="tu"
/// trades, both of which are identical.
/// - "te" trades arrive marginally faster.
/// - Therefore, tag="tu" trades are filtered out and considered only as additional Heartbeats.
///
/// See docs: <https://docs.bitfinex.com/reference/ws-public-trades>
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Serialize)]
pub struct BitfinexTrade {
    pub id: u64,
    pub time: DateTime<Utc>,
    pub side: Side,
    pub price: f64,
    pub amount: f64,
}

impl From<(ExchangeId, Instrument, BitfinexTrade)> for MarketIter<PublicTrade> {
    fn from((exchange_id, instrument, trade): (ExchangeId, Instrument, BitfinexTrade)) -> Self {
        Self(vec![Ok(MarketEvent {
            exchange_time: trade.time,
            received_time: Utc::now(),
            exchange: Exchange::from(exchange_id),
            instrument,
            kind: PublicTrade {
                id: trade.id.to_string(),
                price: trade.price,
                amount: trade.amount,
                side: trade.side,
            },
        })])
    }
}

impl<'de> serde::Deserialize<'de> for BitfinexTrade {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'de>,
    {
        struct SeqVisitor;

        impl<'de> serde::de::Visitor<'de> for SeqVisitor {
            type Value = BitfinexTrade;

            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                formatter.write_str("BitfinexTrade struct from the Bitfinex WebSocket API")
            }

            fn visit_seq<SeqAccessor>(
                self,
                mut seq: SeqAccessor,
            ) -> Result<Self::Value, SeqAccessor::Error>
            where
                SeqAccessor: serde::de::SeqAccess<'de>,
            {
                // Trade: [ID, TIME, AMOUNT,PRICE]
                let id = extract_next(&mut seq, "id")?;
                let time_millis = extract_next(&mut seq, "time")?;
                let amount: f64 = extract_next(&mut seq, "amount")?;
                let price = extract_next(&mut seq, "price")?;
                let side = match amount.is_sign_positive() {
                    true => Side::Buy,
                    false => Side::Sell,
                };

                // Ignore any additional elements or SerDe will fail
                //  '--> Bitfinex may add fields without warning
                while seq.next_element::<serde::de::IgnoredAny>()?.is_some() {}

                Ok(BitfinexTrade {
                    id,
                    time: datetime_utc_from_epoch_duration(std::time::Duration::from_millis(
                        time_millis,
                    )),
                    price,
                    amount: amount.abs(),
                    side,
                })
            }
        }

        // Use Visitor implementation to deserialise the BitfinexTrade message
        deserializer.deserialize_seq(SeqVisitor)
    }
}