rustrade_data/exchange/kraken/
mod.rs1use self::{
2 book::l1::KrakenOrderBookL1, channel::KrakenChannel, market::KrakenMarket,
3 message::KrakenMessage, subscription::KrakenSubResponse, trade::KrakenTrades,
4};
5use crate::{
6 ExchangeWsStream, NoInitialSnapshots,
7 exchange::{Connector, ExchangeSub, StreamSelector},
8 instrument::InstrumentData,
9 subscriber::{WebSocketSubscriber, validator::WebSocketSubValidator},
10 subscription::{book::OrderBooksL1, trade::PublicTrades},
11 transformer::stateless::StatelessTransformer,
12};
13use derive_more::Display;
14use rustrade_instrument::exchange::ExchangeId;
15use rustrade_integration::{
16 protocol::websocket::{WebSocketSerdeParser, WsMessage},
17 subscription::SubscriptionId,
18};
19use rustrade_macro::{DeExchange, SerExchange};
20use serde_json::json;
21use url::Url;
22
23pub mod book;
25
26pub mod channel;
29
30pub mod market;
33
34pub mod message;
36
37pub mod subscription;
40
41pub mod trade;
43
44pub const BASE_URL_KRAKEN: &str = "wss://ws.kraken.com/v2";
48
49pub type KrakenWsStream<Transformer> = ExchangeWsStream<WebSocketSerdeParser, Transformer>;
51
52#[derive(
59 Copy,
60 Clone,
61 Eq,
62 PartialEq,
63 Ord,
64 PartialOrd,
65 Hash,
66 Debug,
67 Default,
68 Display,
69 DeExchange,
70 SerExchange,
71)]
72pub struct Kraken;
73
74impl Connector for Kraken {
75 const ID: ExchangeId = ExchangeId::Kraken;
76 type Channel = KrakenChannel;
77 type Market = KrakenMarket;
78 type Subscriber = WebSocketSubscriber;
79 type SubValidator = WebSocketSubValidator;
80 type SubResponse = KrakenSubResponse;
81
82 fn url() -> Result<Url, url::ParseError> {
83 Url::parse(BASE_URL_KRAKEN)
84 }
85
86 fn requests(exchange_subs: Vec<ExchangeSub<Self::Channel, Self::Market>>) -> Vec<WsMessage> {
87 exchange_subs
88 .into_iter()
89 .map(|ExchangeSub { channel, market }| {
90 let mut params = json!({
91 "channel": channel.as_ref(),
92 "symbol": [market.as_ref()],
93 });
94 if channel == KrakenChannel::ORDER_BOOK_L1 {
95 params["event_trigger"] = json!("bbo");
98 params["snapshot"] = json!(true);
99 } else if channel == KrakenChannel::TRADES {
100 params["snapshot"] = json!(false);
102 }
103
104 WsMessage::text(
105 json!({
106 "method": "subscribe",
107 "params": params,
108 })
109 .to_string(),
110 )
111 })
112 .collect()
113 }
114
115 fn acknowledged_subscription(response: &Self::SubResponse) -> Option<SubscriptionId> {
116 response.subscription_id()
117 }
118}
119
120impl<Instrument> StreamSelector<Instrument, PublicTrades> for Kraken
121where
122 Instrument: InstrumentData,
123{
124 type SnapFetcher = NoInitialSnapshots;
125 type Stream =
126 KrakenWsStream<StatelessTransformer<Self, Instrument::Key, PublicTrades, KrakenTrades>>;
127}
128
129impl<Instrument> StreamSelector<Instrument, OrderBooksL1> for Kraken
130where
131 Instrument: InstrumentData,
132{
133 type SnapFetcher = NoInitialSnapshots;
134 type Stream = KrakenWsStream<
135 StatelessTransformer<Self, Instrument::Key, OrderBooksL1, KrakenOrderBookL1>,
136 >;
137}
138
139#[cfg(test)]
140#[allow(clippy::unwrap_used)] mod tests {
142 use super::*;
143 use crate::exchange::kraken::market::KrakenMarket;
144
145 fn request(channel: KrakenChannel) -> serde_json::Value {
146 let request = Kraken::requests(vec![ExchangeSub {
147 channel,
148 market: KrakenMarket("BTC/USD".into()),
149 }])
150 .remove(0);
151 serde_json::from_str(request.to_text().unwrap()).unwrap()
152 }
153
154 #[test]
155 fn requests_subscribe_in_the_v2_shape() {
156 assert_eq!(
157 request(KrakenChannel::TRADES),
158 json!({
159 "method": "subscribe",
160 "params": {"channel": "trade", "symbol": ["BTC/USD"], "snapshot": false},
161 })
162 );
163 assert_eq!(
164 request(KrakenChannel::ORDER_BOOK_L1),
165 json!({
166 "method": "subscribe",
167 "params": {
168 "channel": "ticker",
169 "symbol": ["BTC/USD"],
170 "event_trigger": "bbo",
171 "snapshot": true,
172 },
173 })
174 );
175 }
176}