barter_data/exchange/kraken/book/
l1.rs1use 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
15pub type KrakenOrderBookL1 = KrakenMessage<KrakenOrderBookL1Inner>;
18
19#[derive(Clone, PartialEq, PartialOrd, Debug, Serialize)]
26pub struct KrakenOrderBookL1Inner {
27 pub subscription_id: SubscriptionId,
28 pub spread: KrakenSpread,
29}
30
31#[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 let _: serde::de::IgnoredAny = extract_next(&mut seq, "channelID")?;
128
129 let spread = extract_next(&mut seq, "spread")?;
131
132 let _: serde::de::IgnoredAny = extract_next(&mut seq, "channelName")?;
134
135 let subscription_id = extract_next::<SeqAccessor, String>(&mut seq, "pair")
137 .map(|market| ExchangeSub::from((KrakenChannel::ORDER_BOOK_L1, market)).id())?;
138
139 while seq.next_element::<serde::de::IgnoredAny>()?.is_some() {}
142
143 Ok(KrakenOrderBookL1Inner {
144 subscription_id,
145 spread,
146 })
147 }
148 }
149
150 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 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 }
213 (actual, expected) => {
214 panic!(
216 "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
217 );
218 }
219 }
220 }
221 }
222 }
223}