barter_data/exchange/kraken/book/
l1.rs

1use super::super::KrakenMessage;
2use crate::{
3    Identifier,
4    books::Level,
5    event::{MarketEvent, MarketIter},
6    exchange::{kraken::channel::KrakenChannel, subscription::ExchangeSub},
7    subscription::book::OrderBookL1,
8};
9use barter_instrument::exchange::ExchangeId;
10use barter_integration::{de::extract_next, subscription::SubscriptionId};
11use chrono::{DateTime, Utc};
12use rust_decimal::Decimal;
13use serde::{Deserialize, Serialize};
14
15/// Terse type alias for an [`Kraken`](super::super::Kraken) real-time OrderBook Level1
16/// (top of books) WebSocket message.
17pub type KrakenOrderBookL1 = KrakenMessage<KrakenOrderBookL1Inner>;
18
19/// [`Kraken`](super::super::Kraken) real-time OrderBook Level1 (top of books) data and the
20/// associated [`SubscriptionId`].
21///
22/// See [`KrakenMessage`] for full raw payload examples.
23///
24/// See docs: <https://docs.kraken.com/websockets/#message-spread>
25#[derive(Clone, PartialEq, PartialOrd, Debug, Serialize)]
26pub struct KrakenOrderBookL1Inner {
27    pub subscription_id: SubscriptionId,
28    pub spread: KrakenSpread,
29}
30
31/// [`Kraken`](super::super::Kraken) best bid and ask.
32///
33/// See [`KrakenMessage`] for full raw payload examples.
34///
35/// See docs: <https://docs.kraken.com/websockets/#message-spread>
36#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
37pub struct KrakenSpread {
38    #[serde(with = "rust_decimal::serde::str")]
39    pub best_bid_price: Decimal,
40    #[serde(with = "rust_decimal::serde::str")]
41    pub best_ask_price: Decimal,
42    #[serde(deserialize_with = "barter_integration::de::de_str_f64_epoch_s_as_datetime_utc")]
43    pub time: DateTime<Utc>,
44    #[serde(with = "rust_decimal::serde::str")]
45    pub best_bid_amount: Decimal,
46    #[serde(with = "rust_decimal::serde::str")]
47    pub best_ask_amount: Decimal,
48}
49
50impl Identifier<Option<SubscriptionId>> for KrakenOrderBookL1Inner {
51    fn id(&self) -> Option<SubscriptionId> {
52        Some(self.subscription_id.clone())
53    }
54}
55
56impl<InstrumentKey> From<(ExchangeId, InstrumentKey, KrakenOrderBookL1)>
57    for MarketIter<InstrumentKey, OrderBookL1>
58{
59    fn from(
60        (exchange_id, instrument, book): (ExchangeId, InstrumentKey, KrakenOrderBookL1),
61    ) -> Self {
62        match book {
63            KrakenOrderBookL1::Data(book) => {
64                let best_ask = if book.spread.best_ask_price.is_zero() {
65                    None
66                } else {
67                    Some(Level::new(
68                        book.spread.best_ask_price,
69                        book.spread.best_ask_amount,
70                    ))
71                };
72
73                let best_bid = if book.spread.best_bid_price.is_zero() {
74                    None
75                } else {
76                    Some(Level::new(
77                        book.spread.best_bid_price,
78                        book.spread.best_bid_amount,
79                    ))
80                };
81
82                Self(vec![Ok(MarketEvent {
83                    time_exchange: book.spread.time,
84                    time_received: Utc::now(),
85                    exchange: exchange_id,
86                    instrument,
87                    kind: OrderBookL1 {
88                        last_update_time: book.spread.time,
89                        best_bid,
90                        best_ask,
91                    },
92                })])
93            }
94            KrakenOrderBookL1::Event(_) => MarketIter(vec![]),
95        }
96    }
97}
98
99impl<'de> serde::de::Deserialize<'de> for KrakenOrderBookL1Inner {
100    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
101    where
102        D: serde::Deserializer<'de>,
103    {
104        struct SeqVisitor;
105
106        impl<'de> serde::de::Visitor<'de> for SeqVisitor {
107            type Value = KrakenOrderBookL1Inner;
108
109            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110                formatter.write_str("KrakenOrderBookL1Inner struct from the Kraken WebSocket API")
111            }
112
113            fn visit_seq<SeqAccessor>(
114                self,
115                mut seq: SeqAccessor,
116            ) -> Result<Self::Value, SeqAccessor::Error>
117            where
118                SeqAccessor: serde::de::SeqAccess<'de>,
119            {
120                // KrakenOrderBookL1Inner Sequence Format:
121                // [channelID, [bid, ask, timestamp, bidVolume, askVolume], channelName, pair]
122                // <https://docs.kraken.com/websockets/#message-book>
123
124                // Extract deprecated channelID & ignore
125                let _: serde::de::IgnoredAny = extract_next(&mut seq, "channelID")?;
126
127                // Extract spread
128                let spread = extract_next(&mut seq, "spread")?;
129
130                // Extract channelName (eg/ "spread") & ignore
131                let _: serde::de::IgnoredAny = extract_next(&mut seq, "channelName")?;
132
133                // Extract pair (eg/ "XBT/USD") & map to SubscriptionId (ie/ "spread|{pair}")
134                let subscription_id = extract_next::<SeqAccessor, String>(&mut seq, "pair")
135                    .map(|market| ExchangeSub::from((KrakenChannel::ORDER_BOOK_L1, market)).id())?;
136
137                // Ignore any additional elements or SerDe will fail
138                //  '--> Exchange may add fields without warning
139                while seq.next_element::<serde::de::IgnoredAny>()?.is_some() {}
140
141                Ok(KrakenOrderBookL1Inner {
142                    subscription_id,
143                    spread,
144                })
145            }
146        }
147
148        // Use Visitor implementation to deserialize the KrakenOrderBookL1Inner
149        deserializer.deserialize_seq(SeqVisitor)
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    mod de {
158        use super::*;
159        use barter_integration::{
160            de::datetime_utc_from_epoch_duration, error::SocketError, subscription::SubscriptionId,
161        };
162        use rust_decimal_macros::dec;
163
164        #[test]
165        fn test_kraken_message_order_book_l1() {
166            struct TestCase {
167                input: &'static str,
168                expected: Result<KrakenOrderBookL1, SocketError>,
169            }
170
171            let tests = vec![TestCase {
172                // TC0: valid KrakenOrderBookL1::Data(KrakenOrderBookL1Inner)
173                input: r#"
174                    [
175                        0,
176                        [
177                            "5698.40000",
178                            "5700.00000",
179                            "1542057299.545897",
180                            "1.01234567",
181                            "0.98765432"
182                        ],
183                        "spread",
184                        "XBT/USD"
185                    ]
186                    "#,
187                expected: Ok(KrakenOrderBookL1::Data(KrakenOrderBookL1Inner {
188                    subscription_id: SubscriptionId::from("spread|XBT/USD"),
189                    spread: KrakenSpread {
190                        best_bid_price: dec!(5698.40000),
191                        best_bid_amount: dec!(1.01234567),
192                        time: datetime_utc_from_epoch_duration(std::time::Duration::from_secs_f64(
193                            1542057299.545897,
194                        )),
195                        best_ask_price: dec!(5700.00000),
196                        best_ask_amount: dec!(0.98765432),
197                    },
198                })),
199            }];
200
201            for (index, test) in tests.into_iter().enumerate() {
202                let actual = serde_json::from_str::<KrakenOrderBookL1>(test.input);
203                match (actual, test.expected) {
204                    (Ok(actual), Ok(expected)) => {
205                        assert_eq!(actual, expected, "TC{} failed", index)
206                    }
207                    (Err(_), Err(_)) => {
208                        // Test passed
209                    }
210                    (actual, expected) => {
211                        // Test failed
212                        panic!(
213                            "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
214                        );
215                    }
216                }
217            }
218        }
219    }
220}