1use nautilus_model::{
19 data::{IndexPriceUpdate, MarkPriceUpdate, OrderBookDeltas, QuoteTick, TradeTick},
20 events::{OrderAccepted, OrderCanceled, OrderExpired, OrderUpdated},
21 reports::{FillReport, OrderStatusReport},
22};
23use serde::{Deserialize, Serialize};
24use serde_json::Value;
25use strum::{AsRefStr, EnumString};
26use ustr::Ustr;
27
28use crate::common::enums::KrakenOrderSide;
29
30#[derive(Clone, Debug)]
32pub enum KrakenFuturesWsMessage {
33 BookDeltas(OrderBookDeltas),
34 Quote(QuoteTick),
35 Trade(TradeTick),
36 MarkPrice(MarkPriceUpdate),
37 IndexPrice(IndexPriceUpdate),
38 OrderAccepted(OrderAccepted),
39 OrderCanceled(OrderCanceled),
40 OrderExpired(OrderExpired),
41 OrderUpdated(OrderUpdated),
42 OrderStatusReport(Box<OrderStatusReport>),
43 FillReport(Box<FillReport>),
44 Reconnected,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, EnumString, AsRefStr)]
49#[serde(rename_all = "snake_case")]
50#[strum(serialize_all = "snake_case")]
51pub enum KrakenFuturesFeed {
52 Ticker,
53 Trade,
54 TradeSnapshot,
55 Book,
56 BookSnapshot,
57 Heartbeat,
58 OpenOrders,
59 OpenOrdersSnapshot,
60 Fills,
61 FillsSnapshot,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, AsRefStr)]
66#[strum(serialize_all = "snake_case")]
67pub enum KrakenFuturesChannel {
68 Book,
69 Trades,
70 Quotes,
71 Mark,
72 Index,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum KrakenFuturesEvent {
79 Subscribe,
80 Unsubscribe,
81 Subscribed,
82 Unsubscribed,
83 Info,
84 Error,
85 Alert,
86 Challenge,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum KrakenFuturesMessageType {
93 OpenOrdersSnapshot,
95 OpenOrdersCancel,
96 OpenOrdersDelta,
97 FillsSnapshot,
98 FillsDelta,
99 Ticker,
101 TradeSnapshot,
102 Trade,
103 BookSnapshot,
104 BookDelta,
105 Info,
107 Pong,
108 Subscribed,
109 Unsubscribed,
110 Challenge,
111 Heartbeat,
112 Unknown,
113}
114
115#[must_use]
116pub fn classify_futures_message(value: &Value) -> KrakenFuturesMessageType {
117 if let Some(event) = value.get("event").and_then(|v| v.as_str()) {
118 return match event {
119 "info" => KrakenFuturesMessageType::Info,
120 "pong" => KrakenFuturesMessageType::Pong,
121 "subscribed" => KrakenFuturesMessageType::Subscribed,
122 "unsubscribed" => KrakenFuturesMessageType::Unsubscribed,
123 "challenge" => KrakenFuturesMessageType::Challenge,
124 _ => KrakenFuturesMessageType::Unknown,
125 };
126 }
127
128 if let Some(feed) = value.get("feed").and_then(|v| v.as_str()) {
129 return match feed {
130 "heartbeat" => KrakenFuturesMessageType::Heartbeat,
131 "open_orders_snapshot" => KrakenFuturesMessageType::OpenOrdersSnapshot,
132 "open_orders" => {
133 if value.get("is_cancel").and_then(|v| v.as_bool()) == Some(true) {
135 if value.get("order").is_some() {
136 KrakenFuturesMessageType::OpenOrdersDelta
137 } else {
138 KrakenFuturesMessageType::OpenOrdersCancel
139 }
140 } else {
141 KrakenFuturesMessageType::OpenOrdersDelta
142 }
143 }
144 "fills_snapshot" => KrakenFuturesMessageType::FillsSnapshot,
145 "fills" => KrakenFuturesMessageType::FillsDelta,
146 "ticker" => KrakenFuturesMessageType::Ticker,
147 "trade_snapshot" => KrakenFuturesMessageType::TradeSnapshot,
148 "trade" => KrakenFuturesMessageType::Trade,
149 "book_snapshot" => KrakenFuturesMessageType::BookSnapshot,
150 "book" => KrakenFuturesMessageType::BookDelta,
151 _ => KrakenFuturesMessageType::Unknown,
152 };
153 }
154
155 KrakenFuturesMessageType::Unknown
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct KrakenFuturesRequest {
161 pub event: KrakenFuturesEvent,
162 pub feed: KrakenFuturesFeed,
163 pub product_ids: Vec<String>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct KrakenFuturesSubscriptionResponse {
169 pub event: KrakenFuturesEvent,
170 pub feed: KrakenFuturesFeed,
171 pub product_ids: Vec<String>,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct KrakenFuturesErrorResponse {
177 pub event: KrakenFuturesEvent,
178 #[serde(default)]
179 pub message: Option<String>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct KrakenFuturesInfoMessage {
185 pub event: KrakenFuturesEvent,
186 pub version: i32,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct KrakenFuturesHeartbeat {
192 pub feed: KrakenFuturesFeed,
193 pub time: i64,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct KrakenFuturesTickerData {
199 pub feed: KrakenFuturesFeed,
200 pub product_id: Ustr,
201 #[serde(default)]
202 pub time: Option<i64>,
203 #[serde(default)]
204 pub bid: Option<f64>,
205 #[serde(default)]
206 pub ask: Option<f64>,
207 #[serde(default)]
208 pub bid_size: Option<f64>,
209 #[serde(default)]
210 pub ask_size: Option<f64>,
211 #[serde(default)]
212 pub last: Option<f64>,
213 #[serde(default)]
214 pub volume: Option<f64>,
215 #[serde(default)]
216 pub volume_quote: Option<f64>,
217 #[serde(default, rename = "openInterest")]
218 pub open_interest: Option<f64>,
219 #[serde(default)]
220 pub index: Option<f64>,
221 #[serde(default, rename = "markPrice")]
222 pub mark_price: Option<f64>,
223 #[serde(default)]
224 pub change: Option<f64>,
225 #[serde(default)]
226 pub open: Option<f64>,
227 #[serde(default)]
228 pub high: Option<f64>,
229 #[serde(default)]
230 pub low: Option<f64>,
231 #[serde(default)]
232 pub funding_rate: Option<f64>,
233 #[serde(default)]
234 pub funding_rate_prediction: Option<f64>,
235 #[serde(default)]
236 pub relative_funding_rate: Option<f64>,
237 #[serde(default)]
238 pub relative_funding_rate_prediction: Option<f64>,
239 #[serde(default)]
240 pub next_funding_rate_time: Option<f64>,
241 #[serde(default)]
242 pub tag: Option<String>,
243 #[serde(default)]
244 pub pair: Option<String>,
245 #[serde(default)]
246 pub leverage: Option<String>,
247 #[serde(default)]
248 pub dtm: Option<i64>,
249 #[serde(default, rename = "maturityTime")]
250 pub maturity_time: Option<i64>,
251 #[serde(default)]
252 pub suspended: Option<bool>,
253 #[serde(default)]
254 pub post_only: Option<bool>,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct KrakenFuturesTradeData {
260 pub feed: KrakenFuturesFeed,
261 pub product_id: Ustr,
262 #[serde(default)]
263 pub uid: Option<String>,
264 pub side: KrakenOrderSide,
265 #[serde(rename = "type", default)]
266 pub trade_type: Option<String>,
267 pub seq: i64,
268 pub time: i64,
269 pub qty: f64,
270 pub price: f64,
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct KrakenFuturesTradeSnapshot {
276 pub feed: KrakenFuturesFeed,
277 pub product_id: Ustr,
278 pub trades: Vec<KrakenFuturesTradeData>,
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct KrakenFuturesBookSnapshot {
284 pub feed: KrakenFuturesFeed,
285 pub product_id: Ustr,
286 pub timestamp: i64,
287 pub seq: i64,
288 #[serde(default)]
289 pub tick_size: Option<f64>,
290 pub bids: Vec<KrakenFuturesBookLevel>,
291 pub asks: Vec<KrakenFuturesBookLevel>,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct KrakenFuturesBookDelta {
297 pub feed: KrakenFuturesFeed,
298 pub product_id: Ustr,
299 pub side: KrakenOrderSide,
300 pub seq: i64,
301 pub price: f64,
302 pub qty: f64,
303 pub timestamp: i64,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct KrakenFuturesBookLevel {
309 pub price: f64,
310 pub qty: f64,
311}
312
313#[derive(Debug, Clone, Serialize)]
315pub struct KrakenFuturesChallengeRequest {
316 pub event: KrakenFuturesEvent,
317 pub api_key: String,
318}
319
320#[derive(Debug, Clone, Deserialize)]
322pub struct KrakenFuturesChallengeResponse {
323 pub event: KrakenFuturesEvent,
324 pub message: String,
325}
326
327#[derive(Debug, Clone, Serialize)]
329pub struct KrakenFuturesPrivateSubscribeRequest {
330 pub event: KrakenFuturesEvent,
331 pub feed: KrakenFuturesFeed,
332 pub api_key: String,
333 pub original_challenge: String,
334 pub signed_challenge: String,
335}
336
337#[derive(Debug, Clone, Deserialize)]
339pub struct KrakenFuturesOpenOrder {
340 pub instrument: Ustr,
341 pub time: i64,
342 pub last_update_time: i64,
343 pub qty: f64,
344 pub filled: f64,
345 #[serde(default)]
347 pub limit_price: Option<f64>,
348 #[serde(default)]
349 pub stop_price: Option<f64>,
350 #[serde(rename = "type")]
351 pub order_type: String,
352 pub order_id: String,
353 #[serde(default)]
354 pub cli_ord_id: Option<String>,
355 pub direction: i32,
357 #[serde(default)]
358 pub reduce_only: bool,
359 #[serde(default, rename = "triggerSignal")]
360 pub trigger_signal: Option<String>,
361}
362
363#[derive(Debug, Clone, Deserialize)]
365pub struct KrakenFuturesOpenOrdersSnapshot {
366 pub feed: KrakenFuturesFeed,
367 #[serde(default)]
368 pub account: Option<String>,
369 pub orders: Vec<KrakenFuturesOpenOrder>,
370}
371
372#[derive(Debug, Clone, Deserialize)]
375pub struct KrakenFuturesOpenOrdersDelta {
376 pub feed: KrakenFuturesFeed,
377 pub order: KrakenFuturesOpenOrder,
378 pub is_cancel: bool,
379 #[serde(default)]
380 pub reason: Option<String>,
381}
382
383#[derive(Debug, Clone, Deserialize)]
386pub struct KrakenFuturesOpenOrdersCancel {
387 pub feed: KrakenFuturesFeed,
388 pub order_id: String,
389 pub cli_ord_id: Option<String>,
390 pub is_cancel: bool,
391 #[serde(default)]
392 pub reason: Option<String>,
393}
394
395#[derive(Debug, Clone, Deserialize)]
397pub struct KrakenFuturesFill {
398 #[serde(alias = "product_id")]
399 pub instrument: Option<Ustr>,
400 pub time: i64,
401 pub price: f64,
402 pub qty: f64,
403 pub order_id: String,
404 #[serde(default)]
405 pub cli_ord_id: Option<String>,
406 pub fill_id: String,
407 pub fill_type: String,
408 pub buy: bool,
410 #[serde(default)]
411 pub fee_paid: Option<f64>,
412 #[serde(default)]
413 pub fee_currency: Option<String>,
414}
415
416#[derive(Debug, Clone, Deserialize)]
418pub struct KrakenFuturesFillsSnapshot {
419 pub feed: KrakenFuturesFeed,
420 #[serde(default)]
421 pub account: Option<String>,
422 pub fills: Vec<KrakenFuturesFill>,
423}
424
425#[derive(Debug, Clone, Deserialize)]
428pub struct KrakenFuturesFillsDelta {
429 pub feed: KrakenFuturesFeed,
430 #[serde(default)]
431 pub username: Option<String>,
432 pub fills: Vec<KrakenFuturesFill>,
433}
434
435#[cfg(test)]
436mod tests {
437 use rstest::rstest;
438
439 use super::*;
440
441 #[rstest]
442 fn test_deserialize_ticker_data() {
443 let json = r#"{
445 "feed": "ticker",
446 "product_id": "PI_XBTUSD",
447 "time": 1700000000000,
448 "bid": 90650.5,
449 "ask": 90651.0,
450 "bid_size": 10.5,
451 "ask_size": 8.2,
452 "last": 90650.8,
453 "volume": 1234567.89,
454 "index": 90648.5,
455 "markPrice": 90649.2,
456 "funding_rate": 0.0001,
457 "openInterest": 50000000.0
458 }"#;
459
460 let ticker: KrakenFuturesTickerData = serde_json::from_str(json).unwrap();
461 assert_eq!(ticker.feed, KrakenFuturesFeed::Ticker);
462 assert_eq!(ticker.product_id, Ustr::from("PI_XBTUSD"));
463 assert_eq!(ticker.bid, Some(90650.5));
464 assert_eq!(ticker.ask, Some(90651.0));
465 assert_eq!(ticker.index, Some(90648.5));
466 assert_eq!(ticker.mark_price, Some(90649.2));
467 assert_eq!(ticker.funding_rate, Some(0.0001));
468 }
469
470 #[rstest]
471 fn test_serialize_subscribe_request() {
472 let request = KrakenFuturesRequest {
473 event: KrakenFuturesEvent::Subscribe,
474 feed: KrakenFuturesFeed::Ticker,
475 product_ids: vec!["PI_XBTUSD".to_string()],
476 };
477
478 let json = serde_json::to_string(&request).unwrap();
479 assert!(json.contains("\"event\":\"subscribe\""));
480 assert!(json.contains("\"feed\":\"ticker\""));
481 assert!(json.contains("PI_XBTUSD"));
482 }
483
484 #[rstest]
485 fn test_deserialize_ticker_from_fixture() {
486 let json = include_str!("../../../test_data/ws_futures_ticker.json");
487 let ticker: KrakenFuturesTickerData = serde_json::from_str(json).unwrap();
488
489 assert_eq!(ticker.feed, KrakenFuturesFeed::Ticker);
490 assert_eq!(ticker.product_id, Ustr::from("PI_XBTUSD"));
491 assert_eq!(ticker.bid, Some(21978.5));
492 assert_eq!(ticker.ask, Some(21987.0));
493 assert_eq!(ticker.bid_size, Some(2536.0));
494 assert_eq!(ticker.ask_size, Some(13948.0));
495 assert_eq!(ticker.index, Some(21984.54));
496 assert_eq!(ticker.mark_price, Some(21979.68641534714));
497 assert!(ticker.funding_rate.is_some());
498 }
499
500 #[rstest]
501 fn test_deserialize_trade_from_fixture() {
502 let json = include_str!("../../../test_data/ws_futures_trade.json");
503 let trade: KrakenFuturesTradeData = serde_json::from_str(json).unwrap();
504
505 assert_eq!(trade.feed, KrakenFuturesFeed::Trade);
506 assert_eq!(trade.product_id, Ustr::from("PI_XBTUSD"));
507 assert_eq!(trade.side, KrakenOrderSide::Sell);
508 assert_eq!(trade.qty, 15000.0);
509 assert_eq!(trade.price, 34969.5);
510 assert_eq!(trade.seq, 653355);
511 }
512
513 #[rstest]
514 fn test_deserialize_trade_snapshot_from_fixture() {
515 let json = include_str!("../../../test_data/ws_futures_trade_snapshot.json");
516 let snapshot: KrakenFuturesTradeSnapshot = serde_json::from_str(json).unwrap();
517
518 assert_eq!(snapshot.feed, KrakenFuturesFeed::TradeSnapshot);
519 assert_eq!(snapshot.product_id, Ustr::from("PI_XBTUSD"));
520 assert_eq!(snapshot.trades.len(), 2);
521 assert_eq!(snapshot.trades[0].price, 34893.0);
522 assert_eq!(snapshot.trades[1].price, 34891.0);
523 }
524
525 #[rstest]
526 fn test_deserialize_book_snapshot_from_fixture() {
527 let json = include_str!("../../../test_data/ws_futures_book_snapshot.json");
528 let snapshot: KrakenFuturesBookSnapshot = serde_json::from_str(json).unwrap();
529
530 assert_eq!(snapshot.feed, KrakenFuturesFeed::BookSnapshot);
531 assert_eq!(snapshot.product_id, Ustr::from("PI_XBTUSD"));
532 assert_eq!(snapshot.bids.len(), 2);
533 assert_eq!(snapshot.asks.len(), 2);
534 assert_eq!(snapshot.bids[0].price, 34892.5);
535 assert_eq!(snapshot.asks[0].price, 34911.5);
536 }
537
538 #[rstest]
539 fn test_deserialize_book_delta_from_fixture() {
540 let json = include_str!("../../../test_data/ws_futures_book_delta.json");
541 let delta: KrakenFuturesBookDelta = serde_json::from_str(json).unwrap();
542
543 assert_eq!(delta.feed, KrakenFuturesFeed::Book);
544 assert_eq!(delta.product_id, Ustr::from("PI_XBTUSD"));
545 assert_eq!(delta.side, KrakenOrderSide::Sell);
546 assert_eq!(delta.price, 34981.0);
547 assert_eq!(delta.qty, 0.0); }
549
550 #[rstest]
551 fn test_deserialize_open_orders_snapshot_from_fixture() {
552 let json = include_str!("../../../test_data/ws_futures_open_orders_snapshot.json");
553 let snapshot: KrakenFuturesOpenOrdersSnapshot = serde_json::from_str(json).unwrap();
554
555 assert_eq!(snapshot.feed, KrakenFuturesFeed::OpenOrdersSnapshot);
556 assert_eq!(snapshot.orders.len(), 1);
557 assert_eq!(snapshot.orders[0].instrument, Ustr::from("PI_XBTUSD"));
558 assert_eq!(snapshot.orders[0].qty, 1000.0);
559 assert_eq!(snapshot.orders[0].order_type, "stop");
560 }
561
562 #[rstest]
563 fn test_deserialize_open_orders_delta_from_fixture() {
564 let json = include_str!("../../../test_data/ws_futures_open_orders_delta.json");
565 let delta: KrakenFuturesOpenOrdersDelta = serde_json::from_str(json).unwrap();
566
567 assert_eq!(delta.feed, KrakenFuturesFeed::OpenOrders);
568 assert!(!delta.is_cancel);
569 assert_eq!(delta.order.instrument, Ustr::from("PI_XBTUSD"));
570 assert_eq!(delta.order.qty, 304.0);
571 assert_eq!(delta.order.limit_price, Some(10640.0));
572 }
573
574 #[rstest]
575 fn test_deserialize_open_orders_cancel_from_fixture() {
576 let json = include_str!("../../../test_data/ws_futures_open_orders_cancel.json");
577 let cancel: KrakenFuturesOpenOrdersCancel = serde_json::from_str(json).unwrap();
578
579 assert_eq!(cancel.feed, KrakenFuturesFeed::OpenOrders);
580 assert!(cancel.is_cancel);
581 assert_eq!(cancel.order_id, "660c6b23-8007-48c1-a7c9-4893f4572e8c");
582 assert_eq!(cancel.reason, Some("cancelled_by_user".to_string()));
583 assert!(cancel.cli_ord_id.is_none()); }
585
586 #[rstest]
587 fn test_deserialize_fills_snapshot_from_fixture() {
588 let json = include_str!("../../../test_data/ws_futures_fills_snapshot.json");
589 let snapshot: KrakenFuturesFillsSnapshot = serde_json::from_str(json).unwrap();
590
591 assert_eq!(snapshot.feed, KrakenFuturesFeed::FillsSnapshot);
592 assert_eq!(snapshot.fills.len(), 2);
593 assert_eq!(
594 snapshot.fills[0].instrument,
595 Some(Ustr::from("FI_XBTUSD_200925"))
596 );
597 assert!(snapshot.fills[0].buy);
598 assert_eq!(snapshot.fills[0].fill_type, "maker");
599 }
600
601 #[rstest]
602 fn test_classify_ticker_message() {
603 let json = include_str!("../../../test_data/ws_futures_ticker.json");
604 let value: Value = serde_json::from_str(json).unwrap();
605 assert_eq!(
606 classify_futures_message(&value),
607 KrakenFuturesMessageType::Ticker
608 );
609 }
610
611 #[rstest]
612 fn test_classify_trade_message() {
613 let json = include_str!("../../../test_data/ws_futures_trade.json");
614 let value: Value = serde_json::from_str(json).unwrap();
615 assert_eq!(
616 classify_futures_message(&value),
617 KrakenFuturesMessageType::Trade
618 );
619 }
620
621 #[rstest]
622 fn test_classify_trade_snapshot_message() {
623 let json = include_str!("../../../test_data/ws_futures_trade_snapshot.json");
624 let value: Value = serde_json::from_str(json).unwrap();
625 assert_eq!(
626 classify_futures_message(&value),
627 KrakenFuturesMessageType::TradeSnapshot
628 );
629 }
630
631 #[rstest]
632 fn test_classify_book_snapshot_message() {
633 let json = include_str!("../../../test_data/ws_futures_book_snapshot.json");
634 let value: Value = serde_json::from_str(json).unwrap();
635 assert_eq!(
636 classify_futures_message(&value),
637 KrakenFuturesMessageType::BookSnapshot
638 );
639 }
640
641 #[rstest]
642 fn test_classify_book_delta_message() {
643 let json = include_str!("../../../test_data/ws_futures_book_delta.json");
644 let value: Value = serde_json::from_str(json).unwrap();
645 assert_eq!(
646 classify_futures_message(&value),
647 KrakenFuturesMessageType::BookDelta
648 );
649 }
650
651 #[rstest]
652 fn test_classify_open_orders_delta_message() {
653 let json = include_str!("../../../test_data/ws_futures_open_orders_delta.json");
654 let value: Value = serde_json::from_str(json).unwrap();
655 assert_eq!(
656 classify_futures_message(&value),
657 KrakenFuturesMessageType::OpenOrdersDelta
658 );
659 }
660
661 #[rstest]
662 fn test_classify_open_orders_cancel_message() {
663 let json = include_str!("../../../test_data/ws_futures_open_orders_cancel.json");
664 let value: Value = serde_json::from_str(json).unwrap();
665 assert_eq!(
666 classify_futures_message(&value),
667 KrakenFuturesMessageType::OpenOrdersCancel
668 );
669 }
670
671 #[rstest]
672 fn test_classify_heartbeat_message() {
673 let json = r#"{"feed":"heartbeat","time":1700000000000}"#;
674 let value: Value = serde_json::from_str(json).unwrap();
675 assert_eq!(
676 classify_futures_message(&value),
677 KrakenFuturesMessageType::Heartbeat
678 );
679 }
680
681 #[rstest]
682 fn test_classify_info_event() {
683 let json = r#"{"event":"info","version":1}"#;
684 let value: Value = serde_json::from_str(json).unwrap();
685 assert_eq!(
686 classify_futures_message(&value),
687 KrakenFuturesMessageType::Info
688 );
689 }
690
691 #[rstest]
692 fn test_classify_subscribed_event() {
693 let json = r#"{"event":"subscribed","feed":"ticker","product_ids":["PI_XBTUSD"]}"#;
694 let value: Value = serde_json::from_str(json).unwrap();
695 assert_eq!(
696 classify_futures_message(&value),
697 KrakenFuturesMessageType::Subscribed
698 );
699 }
700}