rustrade_data/exchange/kraken/book/
l1.rs1use super::super::{
2 KrakenMessage,
3 channel::KrakenChannel,
4 message::{KrakenChannelData, KrakenData},
5};
6use crate::{
7 books::Level,
8 event::{MarketEvent, MarketIter},
9 subscription::book::OrderBookL1,
10};
11use chrono::{DateTime, Utc};
12use rust_decimal::Decimal;
13use rustrade_instrument::exchange::ExchangeId;
14use serde::{Deserialize, Serialize};
15use smol_str::SmolStr;
16
17pub type KrakenOrderBookL1 = KrakenMessage<KrakenData<KrakenTicker>>;
20
21#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
29pub struct KrakenTicker {
30 pub symbol: SmolStr,
31 #[serde(
32 rename = "bid",
33 deserialize_with = "rust_decimal::serde::float::deserialize"
34 )]
35 pub best_bid_price: Decimal,
36 #[serde(
37 rename = "bid_qty",
38 deserialize_with = "rust_decimal::serde::float::deserialize"
39 )]
40 pub best_bid_amount: Decimal,
41 #[serde(
42 rename = "ask",
43 deserialize_with = "rust_decimal::serde::float::deserialize"
44 )]
45 pub best_ask_price: Decimal,
46 #[serde(
47 rename = "ask_qty",
48 deserialize_with = "rust_decimal::serde::float::deserialize"
49 )]
50 pub best_ask_amount: Decimal,
51 pub timestamp: DateTime<Utc>,
52}
53
54impl KrakenChannelData for KrakenTicker {
55 const CHANNEL: KrakenChannel = KrakenChannel::ORDER_BOOK_L1;
56
57 fn symbol(&self) -> &str {
58 &self.symbol
59 }
60}
61
62impl<InstrumentKey: Clone> From<(ExchangeId, InstrumentKey, KrakenOrderBookL1)>
63 for MarketIter<InstrumentKey, OrderBookL1>
64{
65 fn from(
66 (exchange_id, instrument, book): (ExchangeId, InstrumentKey, KrakenOrderBookL1),
67 ) -> Self {
68 match book {
69 KrakenOrderBookL1::Data(book) => {
70 let time_received = Utc::now();
71 book.data
72 .into_iter()
73 .map(|ticker| {
74 let level = |price: Decimal, amount| {
76 (!price.is_zero()).then(|| Level::new(price, amount))
77 };
78
79 Ok(MarketEvent {
80 time_exchange: ticker.timestamp,
81 time_received,
82 exchange: exchange_id,
83 instrument: instrument.clone(),
84 kind: OrderBookL1 {
85 last_update_time: ticker.timestamp,
86 best_bid: level(ticker.best_bid_price, ticker.best_bid_amount),
87 best_ask: level(ticker.best_ask_price, ticker.best_ask_amount),
88 },
89 })
90 })
91 .collect()
92 }
93 KrakenOrderBookL1::Event(_) => MarketIter(vec![]),
94 }
95 }
96}
97
98#[cfg(test)]
99#[allow(clippy::unwrap_used)] mod tests {
101 use super::*;
102 use chrono::TimeZone;
103 use rust_decimal_macros::dec;
104 use rustrade_integration::subscription::SubscriptionId;
105
106 const TICKER: &str = r#"{"channel":"ticker","type":"update","data":[{"symbol":"BTC/USD","bid":5698.4,"bid_qty":1.01234567,"ask":5700.0,"ask_qty":0.98765432,"last":5699.1,"volume":1234.5,"vwap":5650.2,"low":5600.0,"high":5710.0,"change":12.3,"change_pct":0.22,"timestamp":"2018-11-12T21:14:59.545897Z"}]}"#;
107
108 fn time() -> DateTime<Utc> {
109 Utc.with_ymd_and_hms(2018, 11, 12, 21, 14, 59)
110 .unwrap()
111 .checked_add_signed(chrono::Duration::microseconds(545_897))
112 .unwrap()
113 }
114
115 #[test]
116 fn test_kraken_message_order_book_l1() {
117 let actual: KrakenOrderBookL1 = serde_json::from_str(TICKER).unwrap();
118
119 assert_eq!(
120 actual,
121 KrakenOrderBookL1::Data(KrakenData {
122 subscription_id: Some(SubscriptionId::from("ticker|BTC/USD")),
123 data: vec![KrakenTicker {
124 symbol: "BTC/USD".into(),
125 best_bid_price: dec!(5698.4),
126 best_bid_amount: dec!(1.01234567),
127 best_ask_price: dec!(5700),
128 best_ask_amount: dec!(0.98765432),
129 timestamp: time(),
130 }],
131 })
132 );
133 }
134
135 #[test]
136 fn a_ticker_becomes_the_top_of_book() {
137 let book: KrakenOrderBookL1 = serde_json::from_str(TICKER).unwrap();
138
139 let events = MarketIter::<&str, OrderBookL1>::from((ExchangeId::Kraken, "btc", book)).0;
140
141 assert_eq!(events.len(), 1);
142 let event = events.into_iter().next().unwrap().unwrap();
143 assert_eq!(event.time_exchange, time());
144 assert_eq!(
145 event.kind,
146 OrderBookL1 {
147 last_update_time: time(),
148 best_bid: Some(Level::new(dec!(5698.4), dec!(1.01234567))),
149 best_ask: Some(Level::new(dec!(5700), dec!(0.98765432))),
150 }
151 );
152 }
153
154 #[test]
155 fn a_zero_price_is_an_empty_side() {
156 let input = TICKER.replace(r#""bid":5698.4"#, r#""bid":0"#);
157 let book: KrakenOrderBookL1 = serde_json::from_str(&input).unwrap();
158
159 let events = MarketIter::<&str, OrderBookL1>::from((ExchangeId::Kraken, "btc", book)).0;
160
161 let event = events.into_iter().next().unwrap().unwrap();
162 assert_eq!(event.kind.best_bid, None);
163 assert!(event.kind.best_ask.is_some());
164 }
165}