Skip to main content

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