rustrade_data/exchange/kraken/
trade.rs1use super::{
2 KrakenMessage,
3 channel::KrakenChannel,
4 message::{KrakenChannelData, KrakenData},
5};
6use crate::{
7 event::{MarketEvent, MarketIter},
8 subscription::trade::PublicTrade,
9};
10use chrono::{DateTime, Utc};
11use rust_decimal::Decimal;
12use rustrade_instrument::{Side, exchange::ExchangeId};
13use serde::{Deserialize, Serialize};
14use smol_str::{SmolStr, format_smolstr};
15
16pub type KrakenTrades = KrakenMessage<KrakenData<KrakenTrade>>;
18
19#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
25pub struct KrakenTrade {
26 pub symbol: SmolStr,
27 pub side: Side,
28 #[serde(deserialize_with = "rust_decimal::serde::float::deserialize")]
29 pub price: Decimal,
30 #[serde(
31 rename = "qty",
32 deserialize_with = "rust_decimal::serde::float::deserialize"
33 )]
34 pub amount: Decimal,
35 pub trade_id: u64,
36 pub timestamp: DateTime<Utc>,
37}
38
39impl KrakenChannelData for KrakenTrade {
40 const CHANNEL: KrakenChannel = KrakenChannel::TRADES;
41
42 fn symbol(&self) -> &str {
43 &self.symbol
44 }
45}
46
47impl<InstrumentKey: Clone> From<(ExchangeId, InstrumentKey, KrakenTrades)>
48 for MarketIter<InstrumentKey, PublicTrade>
49{
50 fn from((exchange, instrument, trades): (ExchangeId, InstrumentKey, KrakenTrades)) -> Self {
51 match trades {
52 KrakenTrades::Data(trades) => {
53 let time_received = Utc::now();
54 trades
55 .data
56 .into_iter()
57 .map(|trade| {
58 Ok(MarketEvent {
59 time_exchange: trade.timestamp,
60 time_received,
61 exchange,
62 instrument: instrument.clone(),
63 kind: PublicTrade {
64 id: format_smolstr!("{}", trade.trade_id),
65 price: trade.price,
66 amount: trade.amount,
67 side: Some(trade.side),
68 },
69 })
70 })
71 .collect()
72 }
73 KrakenTrades::Event(_) => Self(vec![]),
74 }
75 }
76}
77
78#[cfg(test)]
79#[allow(clippy::unwrap_used)] mod tests {
81 use super::*;
82 use crate::Identifier;
83 use chrono::TimeZone;
84 use rust_decimal_macros::dec;
85 use rustrade_integration::subscription::SubscriptionId;
86
87 fn trade(symbol: &str, side: &str, price: &str, qty: &str, trade_id: u64) -> String {
88 format!(
89 r#"{{"symbol":"{symbol}","side":"{side}","price":{price},"qty":{qty},"ord_type":"limit","trade_id":{trade_id},"timestamp":"2018-08-18T17:40:57.321597Z"}}"#
90 )
91 }
92
93 fn message(trades: &[String]) -> String {
94 format!(
95 r#"{{"channel":"trade","type":"update","data":[{}]}}"#,
96 trades.join(",")
97 )
98 }
99
100 #[test]
101 fn test_kraken_message_trades() {
102 let input = message(&[
103 trade("BTC/USD", "sell", "5541.2", "0.15850568", 1001),
104 trade("BTC/USD", "buy", "6060", "5.006e-05", 1002),
105 ]);
106
107 let actual: KrakenTrades = serde_json::from_str(&input).unwrap();
108
109 let time = Utc
110 .with_ymd_and_hms(2018, 8, 18, 17, 40, 57)
111 .unwrap()
112 .checked_add_signed(chrono::Duration::microseconds(321_597))
113 .unwrap();
114 assert_eq!(
115 actual,
116 KrakenTrades::Data(KrakenData {
117 subscription_id: Some(SubscriptionId::from("trade|BTC/USD")),
118 data: vec![
119 KrakenTrade {
120 symbol: "BTC/USD".into(),
121 side: Side::Sell,
122 price: dec!(5541.2),
123 amount: dec!(0.15850568),
124 trade_id: 1001,
125 timestamp: time,
126 },
127 KrakenTrade {
128 symbol: "BTC/USD".into(),
129 side: Side::Buy,
130 price: dec!(6060),
131 amount: dec!(0.00005006),
132 trade_id: 1002,
133 timestamp: time,
134 },
135 ],
136 })
137 );
138 }
139
140 #[test]
141 fn trades_become_public_trades_keyed_by_kraken_trade_id() {
142 let input = message(&[trade("ETH/USD", "sell", "2509.41", "0.02625345", 67552113)]);
143 let trades: KrakenTrades = serde_json::from_str(&input).unwrap();
144
145 let events = MarketIter::<&str, PublicTrade>::from((ExchangeId::Kraken, "eth", trades)).0;
146
147 assert_eq!(events.len(), 1);
148 let event = events.into_iter().next().unwrap().unwrap();
149 assert_eq!(event.instrument, "eth");
150 assert_eq!(
151 event.kind,
152 PublicTrade {
153 id: "67552113".into(),
154 price: dec!(2509.41),
155 amount: dec!(0.02625345),
156 side: Some(Side::Sell),
157 }
158 );
159 }
160
161 #[test]
162 fn a_message_without_trades_routes_nowhere() {
163 let trades: KrakenTrades = serde_json::from_str(&message(&[])).unwrap();
164 assert_eq!(trades.id(), None);
165 }
166
167 #[test]
168 fn messages_that_carry_no_trades_route_nowhere() {
169 for message in [
170 r#"{"channel":"heartbeat"}"#,
171 r#"{"channel":"status","type":"update","data":[{"api_version":"v2","connection_id":1,"system":"online","version":"2.0.0"}]}"#,
172 r#"{"method":"pong","time_in":"2018-11-12T21:14:59.545897Z","time_out":"2018-11-12T21:14:59.545901Z"}"#,
173 ] {
174 let trades: KrakenTrades = serde_json::from_str(message).unwrap();
175 assert!(matches!(trades, KrakenMessage::Event(_)), "{message}");
176 assert_eq!(trades.id(), None, "{message}");
177 }
178 }
179}