1use serde::{Deserialize, Deserializer, Serialize};
19use serde_json::Value;
20use strum::{AsRefStr, EnumString};
21use ustr::Ustr;
22
23use crate::common::enums::{KrakenFillType, KrakenFuturesOrderType, KrakenOrderSide};
24
25fn deserialize_optional_price_zero_as_none<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
32where
33 D: Deserializer<'de>,
34{
35 let value = Option::<f64>::deserialize(deserializer)?;
36 Ok(value.filter(|v| *v != 0.0))
37}
38
39#[derive(Clone, Debug)]
41#[expect(
42 clippy::large_enum_variant,
43 reason = "Messages are ephemeral and immediately consumed"
44)]
45pub enum KrakenFuturesWsMessage {
46 Ticker(KrakenFuturesTickerData),
47 Trade(KrakenFuturesTradeData),
48 BookSnapshot(KrakenFuturesBookSnapshot),
49 BookDelta(KrakenFuturesBookDelta),
50 OpenOrdersCancel(KrakenFuturesOpenOrdersCancel),
51 OpenOrdersDelta(KrakenFuturesOpenOrdersDelta),
52 FillsDelta(KrakenFuturesFillsDelta),
53 Challenge(String),
54 Reconnected,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, EnumString, AsRefStr)]
59#[serde(rename_all = "snake_case")]
60#[strum(serialize_all = "snake_case")]
61pub enum KrakenFuturesFeed {
62 Ticker,
63 Trade,
64 TradeSnapshot,
65 Book,
66 BookSnapshot,
67 Heartbeat,
68 OpenOrders,
69 OpenOrdersSnapshot,
70 Fills,
71 FillsSnapshot,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, AsRefStr)]
76#[strum(serialize_all = "snake_case")]
77pub enum KrakenFuturesChannel {
78 Book,
79 Deltas,
80 Trades,
81 Quotes,
82 Mark,
83 Index,
84 Funding,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "snake_case")]
90pub enum KrakenFuturesEvent {
91 Subscribe,
92 Unsubscribe,
93 Subscribed,
94 Unsubscribed,
95 Info,
96 Error,
97 Alert,
98 Challenge,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum KrakenFuturesMessageType {
105 OpenOrdersSnapshot,
107 OpenOrdersCancel,
108 OpenOrdersDelta,
109 FillsSnapshot,
110 FillsDelta,
111 Ticker,
113 TradeSnapshot,
114 Trade,
115 BookSnapshot,
116 BookDelta,
117 Info,
119 Pong,
120 Subscribed,
121 Unsubscribed,
122 Challenge,
123 Heartbeat,
124 Error,
125 Alert,
126 Unknown,
127}
128
129#[must_use]
130pub fn classify_futures_message(value: &Value) -> KrakenFuturesMessageType {
131 if let Some(event) = value.get("event").and_then(|v| v.as_str()) {
132 return match event {
133 "info" => KrakenFuturesMessageType::Info,
134 "pong" => KrakenFuturesMessageType::Pong,
135 "subscribed" => KrakenFuturesMessageType::Subscribed,
136 "unsubscribed" => KrakenFuturesMessageType::Unsubscribed,
137 "challenge" => KrakenFuturesMessageType::Challenge,
138 "error" => KrakenFuturesMessageType::Error,
139 "alert" => KrakenFuturesMessageType::Alert,
140 _ => KrakenFuturesMessageType::Unknown,
141 };
142 }
143
144 if let Some(feed) = value.get("feed").and_then(|v| v.as_str()) {
145 return match feed {
146 "heartbeat" => KrakenFuturesMessageType::Heartbeat,
147 "open_orders_snapshot" => KrakenFuturesMessageType::OpenOrdersSnapshot,
148 "open_orders" => {
149 if value.get("is_cancel").and_then(|v| v.as_bool()) == Some(true) {
151 if value.get("order").is_some() {
152 KrakenFuturesMessageType::OpenOrdersDelta
153 } else {
154 KrakenFuturesMessageType::OpenOrdersCancel
155 }
156 } else {
157 KrakenFuturesMessageType::OpenOrdersDelta
158 }
159 }
160 "fills_snapshot" => KrakenFuturesMessageType::FillsSnapshot,
161 "fills" => KrakenFuturesMessageType::FillsDelta,
162 "ticker" => KrakenFuturesMessageType::Ticker,
163 "trade_snapshot" => KrakenFuturesMessageType::TradeSnapshot,
164 "trade" => KrakenFuturesMessageType::Trade,
165 "book_snapshot" => KrakenFuturesMessageType::BookSnapshot,
166 "book" => KrakenFuturesMessageType::BookDelta,
167 _ => KrakenFuturesMessageType::Unknown,
168 };
169 }
170
171 KrakenFuturesMessageType::Unknown
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct KrakenFuturesRequest {
177 pub event: KrakenFuturesEvent,
178 pub feed: KrakenFuturesFeed,
179 pub product_ids: Vec<String>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct KrakenFuturesSubscriptionResponse {
185 pub event: KrakenFuturesEvent,
186 pub feed: KrakenFuturesFeed,
187 pub product_ids: Vec<String>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct KrakenFuturesErrorResponse {
193 pub event: KrakenFuturesEvent,
194 #[serde(default)]
195 pub message: Option<String>,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct KrakenFuturesInfoMessage {
201 pub event: KrakenFuturesEvent,
202 pub version: i32,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct KrakenFuturesHeartbeat {
208 pub feed: KrakenFuturesFeed,
209 pub time: i64,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct KrakenFuturesTickerData {
215 pub feed: KrakenFuturesFeed,
216 pub product_id: Ustr,
217 #[serde(default)]
218 pub time: Option<i64>,
219 #[serde(default)]
220 pub bid: Option<f64>,
221 #[serde(default)]
222 pub ask: Option<f64>,
223 #[serde(default)]
224 pub bid_size: Option<f64>,
225 #[serde(default)]
226 pub ask_size: Option<f64>,
227 #[serde(default)]
228 pub last: Option<f64>,
229 #[serde(default)]
230 pub volume: Option<f64>,
231 #[serde(default)]
232 pub volume_quote: Option<f64>,
233 #[serde(default, rename = "openInterest")]
234 pub open_interest: Option<f64>,
235 #[serde(default)]
236 pub index: Option<f64>,
237 #[serde(default, rename = "markPrice")]
238 pub mark_price: Option<f64>,
239 #[serde(default)]
240 pub change: Option<f64>,
241 #[serde(default)]
242 pub open: Option<f64>,
243 #[serde(default)]
244 pub high: Option<f64>,
245 #[serde(default)]
246 pub low: Option<f64>,
247 #[serde(default)]
248 pub funding_rate: Option<f64>,
249 #[serde(default)]
250 pub funding_rate_prediction: Option<f64>,
251 #[serde(default)]
252 pub relative_funding_rate: Option<f64>,
253 #[serde(default)]
254 pub relative_funding_rate_prediction: Option<f64>,
255 #[serde(default)]
256 pub next_funding_rate_time: Option<f64>,
257 #[serde(default)]
258 pub tag: Option<String>,
259 #[serde(default)]
260 pub pair: Option<String>,
261 #[serde(default)]
262 pub leverage: Option<String>,
263 #[serde(default)]
264 pub dtm: Option<i64>,
265 #[serde(default, rename = "maturityTime")]
266 pub maturity_time: Option<i64>,
267 #[serde(default)]
268 pub suspended: Option<bool>,
269 #[serde(default)]
270 pub post_only: Option<bool>,
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct KrakenFuturesTradeData {
276 pub feed: KrakenFuturesFeed,
277 pub product_id: Ustr,
278 #[serde(default)]
279 pub uid: Option<String>,
280 pub side: KrakenOrderSide,
281 #[serde(rename = "type", default)]
282 pub trade_type: Option<String>,
283 pub seq: i64,
284 pub time: i64,
285 pub qty: f64,
286 pub price: f64,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct KrakenFuturesTradeSnapshot {
292 pub feed: KrakenFuturesFeed,
293 pub product_id: Ustr,
294 pub trades: Vec<KrakenFuturesTradeData>,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct KrakenFuturesBookSnapshot {
300 pub feed: KrakenFuturesFeed,
301 pub product_id: Ustr,
302 pub timestamp: i64,
303 pub seq: i64,
304 #[serde(default, rename = "tickSize")]
305 pub tick_size: Option<f64>,
306 pub bids: Vec<KrakenFuturesBookLevel>,
307 pub asks: Vec<KrakenFuturesBookLevel>,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct KrakenFuturesBookDelta {
313 pub feed: KrakenFuturesFeed,
314 pub product_id: Ustr,
315 pub side: KrakenOrderSide,
316 pub seq: i64,
317 pub price: f64,
318 pub qty: f64,
319 pub timestamp: i64,
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct KrakenFuturesBookLevel {
325 pub price: f64,
326 pub qty: f64,
327}
328
329#[derive(Debug, Clone, Serialize)]
331pub struct KrakenFuturesChallengeRequest {
332 pub event: KrakenFuturesEvent,
333 pub api_key: String,
334}
335
336#[derive(Debug, Clone, Deserialize)]
338pub struct KrakenFuturesChallengeResponse {
339 pub event: KrakenFuturesEvent,
340 pub message: String,
341}
342
343#[derive(Debug, Clone, Serialize)]
345pub struct KrakenFuturesPrivateSubscribeRequest {
346 pub event: KrakenFuturesEvent,
347 pub feed: KrakenFuturesFeed,
348 pub api_key: String,
349 pub original_challenge: String,
350 pub signed_challenge: String,
351}
352
353#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct KrakenFuturesOpenOrder {
356 pub instrument: Ustr,
357 pub time: i64,
358 pub last_update_time: i64,
359 pub qty: f64,
360 pub filled: f64,
361 #[serde(default, deserialize_with = "deserialize_optional_price_zero_as_none")]
363 pub limit_price: Option<f64>,
364 #[serde(default, deserialize_with = "deserialize_optional_price_zero_as_none")]
365 pub stop_price: Option<f64>,
366 #[serde(rename = "type")]
367 pub order_type: KrakenFuturesOrderType,
368 pub order_id: String,
369 #[serde(default)]
370 pub cli_ord_id: Option<String>,
371 pub direction: i32,
373 #[serde(default)]
374 pub reduce_only: bool,
375 #[serde(default, rename = "triggerSignal")]
376 pub trigger_signal: Option<String>,
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct KrakenFuturesOpenOrdersSnapshot {
382 pub feed: KrakenFuturesFeed,
383 #[serde(default)]
384 pub account: Option<String>,
385 pub orders: Vec<KrakenFuturesOpenOrder>,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
391pub struct KrakenFuturesOpenOrdersDelta {
392 pub feed: KrakenFuturesFeed,
393 pub order: KrakenFuturesOpenOrder,
394 pub is_cancel: bool,
395 #[serde(default)]
396 pub reason: Option<String>,
397}
398
399impl KrakenFuturesOpenOrdersDelta {
400 #[must_use]
408 pub fn is_fill_driven_cancel(&self) -> bool {
409 self.is_cancel && matches!(self.reason.as_deref(), Some("full_fill" | "partial_fill"))
410 }
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct KrakenFuturesOpenOrdersCancel {
417 pub feed: KrakenFuturesFeed,
418 pub order_id: String,
419 pub cli_ord_id: Option<String>,
420 pub is_cancel: bool,
421 #[serde(default)]
422 pub reason: Option<String>,
423}
424
425#[derive(Debug, Clone, Serialize, Deserialize)]
427pub struct KrakenFuturesFill {
428 #[serde(alias = "product_id")]
429 pub instrument: Option<Ustr>,
430 pub time: i64,
431 pub price: f64,
432 pub qty: f64,
433 pub order_id: String,
434 #[serde(default)]
435 pub cli_ord_id: Option<String>,
436 pub fill_id: String,
437 pub fill_type: KrakenFillType,
438 pub buy: bool,
440 #[serde(default)]
441 pub fee_paid: Option<f64>,
442 #[serde(default)]
443 pub fee_currency: Option<String>,
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize)]
448pub struct KrakenFuturesFillsSnapshot {
449 pub feed: KrakenFuturesFeed,
450 #[serde(default)]
451 pub account: Option<String>,
452 pub fills: Vec<KrakenFuturesFill>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
458pub struct KrakenFuturesFillsDelta {
459 pub feed: KrakenFuturesFeed,
460 #[serde(default)]
461 pub username: Option<String>,
462 pub fills: Vec<KrakenFuturesFill>,
463}
464
465#[cfg(test)]
466mod tests {
467 use rstest::rstest;
468
469 use super::*;
470
471 #[rstest]
472 fn test_deserialize_ticker_data() {
473 let json = r#"{
475 "feed": "ticker",
476 "product_id": "PI_XBTUSD",
477 "time": 1700000000000,
478 "bid": 90650.5,
479 "ask": 90651.0,
480 "bid_size": 10.5,
481 "ask_size": 8.2,
482 "last": 90650.8,
483 "volume": 1234567.89,
484 "index": 90648.5,
485 "markPrice": 90649.2,
486 "funding_rate": 0.0001,
487 "openInterest": 50000000.0
488 }"#;
489
490 let ticker: KrakenFuturesTickerData = serde_json::from_str(json).unwrap();
491 assert_eq!(ticker.feed, KrakenFuturesFeed::Ticker);
492 assert_eq!(ticker.product_id, Ustr::from("PI_XBTUSD"));
493 assert_eq!(ticker.bid, Some(90650.5));
494 assert_eq!(ticker.ask, Some(90651.0));
495 assert_eq!(ticker.index, Some(90648.5));
496 assert_eq!(ticker.mark_price, Some(90649.2));
497 assert_eq!(ticker.funding_rate, Some(0.0001));
498 }
499
500 #[rstest]
501 fn test_serialize_subscribe_request() {
502 let request = KrakenFuturesRequest {
503 event: KrakenFuturesEvent::Subscribe,
504 feed: KrakenFuturesFeed::Ticker,
505 product_ids: vec!["PI_XBTUSD".to_string()],
506 };
507
508 let json = serde_json::to_string(&request).unwrap();
509 assert!(json.contains("\"event\":\"subscribe\""));
510 assert!(json.contains("\"feed\":\"ticker\""));
511 assert!(json.contains("PI_XBTUSD"));
512 }
513
514 #[rstest]
515 fn test_deserialize_ticker_from_fixture() {
516 let json = include_str!("../../../test_data/ws_futures_ticker.json");
517 let ticker: KrakenFuturesTickerData = serde_json::from_str(json).unwrap();
518
519 assert_eq!(ticker.feed, KrakenFuturesFeed::Ticker);
520 assert_eq!(ticker.product_id, Ustr::from("PI_XBTUSD"));
521 assert_eq!(ticker.bid, Some(21978.5));
522 assert_eq!(ticker.ask, Some(21987.0));
523 assert_eq!(ticker.bid_size, Some(2536.0));
524 assert_eq!(ticker.ask_size, Some(13948.0));
525 assert_eq!(ticker.index, Some(21984.54));
526 assert_eq!(ticker.mark_price, Some(21979.68641534714));
527 assert!(ticker.funding_rate.is_some());
528 }
529
530 #[rstest]
531 fn test_deserialize_trade_from_fixture() {
532 let json = include_str!("../../../test_data/ws_futures_trade.json");
533 let trade: KrakenFuturesTradeData = serde_json::from_str(json).unwrap();
534
535 assert_eq!(trade.feed, KrakenFuturesFeed::Trade);
536 assert_eq!(trade.product_id, Ustr::from("PI_XBTUSD"));
537 assert_eq!(trade.side, KrakenOrderSide::Sell);
538 assert_eq!(trade.qty, 15000.0);
539 assert_eq!(trade.price, 34969.5);
540 assert_eq!(trade.seq, 653355);
541 }
542
543 #[rstest]
544 fn test_deserialize_trade_snapshot_from_fixture() {
545 let json = include_str!("../../../test_data/ws_futures_trade_snapshot.json");
546 let snapshot: KrakenFuturesTradeSnapshot = serde_json::from_str(json).unwrap();
547
548 assert_eq!(snapshot.feed, KrakenFuturesFeed::TradeSnapshot);
549 assert_eq!(snapshot.product_id, Ustr::from("PI_XBTUSD"));
550 assert_eq!(snapshot.trades.len(), 2);
551 assert_eq!(snapshot.trades[0].price, 34893.0);
552 assert_eq!(snapshot.trades[1].price, 34891.0);
553 }
554
555 #[rstest]
556 fn test_deserialize_book_snapshot_from_fixture() {
557 let json = include_str!("../../../test_data/ws_futures_book_snapshot.json");
558 let snapshot: KrakenFuturesBookSnapshot = serde_json::from_str(json).unwrap();
559
560 assert_eq!(snapshot.feed, KrakenFuturesFeed::BookSnapshot);
561 assert_eq!(snapshot.product_id, Ustr::from("PI_XBTUSD"));
562 assert_eq!(snapshot.bids.len(), 2);
563 assert_eq!(snapshot.asks.len(), 2);
564 assert_eq!(snapshot.bids[0].price, 34892.5);
565 assert_eq!(snapshot.asks[0].price, 34911.5);
566 }
567
568 #[rstest]
569 fn test_deserialize_book_delta_from_fixture() {
570 let json = include_str!("../../../test_data/ws_futures_book_delta.json");
571 let delta: KrakenFuturesBookDelta = serde_json::from_str(json).unwrap();
572
573 assert_eq!(delta.feed, KrakenFuturesFeed::Book);
574 assert_eq!(delta.product_id, Ustr::from("PI_XBTUSD"));
575 assert_eq!(delta.side, KrakenOrderSide::Sell);
576 assert_eq!(delta.price, 34981.0);
577 assert_eq!(delta.qty, 0.0); }
579
580 #[rstest]
581 fn test_deserialize_open_orders_snapshot_from_fixture() {
582 let json = include_str!("../../../test_data/ws_futures_open_orders_snapshot.json");
583 let snapshot: KrakenFuturesOpenOrdersSnapshot = serde_json::from_str(json).unwrap();
584
585 assert_eq!(snapshot.feed, KrakenFuturesFeed::OpenOrdersSnapshot);
586 assert_eq!(snapshot.orders.len(), 1);
587 assert_eq!(snapshot.orders[0].instrument, Ustr::from("PI_XBTUSD"));
588 assert_eq!(snapshot.orders[0].qty, 1000.0);
589 assert_eq!(
590 snapshot.orders[0].order_type,
591 KrakenFuturesOrderType::StopLower
592 );
593 }
594
595 #[rstest]
596 fn test_deserialize_open_orders_delta_from_fixture() {
597 let json = include_str!("../../../test_data/ws_futures_open_orders_delta.json");
598 let delta: KrakenFuturesOpenOrdersDelta = serde_json::from_str(json).unwrap();
599
600 assert_eq!(delta.feed, KrakenFuturesFeed::OpenOrders);
601 assert!(!delta.is_cancel);
602 assert_eq!(delta.order.instrument, Ustr::from("PI_XBTUSD"));
603 assert_eq!(delta.order.qty, 304.0);
604 assert_eq!(delta.order.limit_price, Some(10640.0));
605 assert_eq!(delta.order.stop_price, None);
609 }
610
611 #[rstest]
612 fn test_deserialize_open_orders_delta_full_fill_is_fill_driven_cancel() {
613 let json = include_str!("../../../test_data/ws_futures_open_orders_delta_full_fill.json");
618 let delta: KrakenFuturesOpenOrdersDelta = serde_json::from_str(json).unwrap();
619
620 assert!(delta.is_cancel);
621 assert_eq!(delta.reason.as_deref(), Some("full_fill"));
622 assert_eq!(delta.order.qty, 0.0);
623 assert_eq!(delta.order.filled, 0.0001);
624 assert!(delta.is_fill_driven_cancel());
625 }
626
627 #[rstest]
628 #[case::placement(false, None, false)]
629 #[case::user_cancel(true, Some("cancelled_by_user"), false)]
630 #[case::post_only_reject(true, Some("post_order_failed_because_it_would_filled"), false)]
631 #[case::full_fill(true, Some("full_fill"), true)]
632 #[case::partial_fill(true, Some("partial_fill"), true)]
633 #[case::cancel_no_reason(true, None, false)]
634 fn test_open_orders_delta_is_fill_driven_cancel(
635 #[case] is_cancel: bool,
636 #[case] reason: Option<&'static str>,
637 #[case] expected: bool,
638 ) {
639 let delta = KrakenFuturesOpenOrdersDelta {
640 feed: KrakenFuturesFeed::OpenOrders,
641 order: KrakenFuturesOpenOrder {
642 instrument: Ustr::from("PF_XBTUSD"),
643 time: 0,
644 last_update_time: 0,
645 qty: 0.0001,
646 filled: 0.0,
647 limit_price: Some(70_000.0),
648 stop_price: None,
649 order_type: KrakenFuturesOrderType::Limit,
650 order_id: "test".to_string(),
651 cli_ord_id: None,
652 direction: 0,
653 reduce_only: false,
654 trigger_signal: None,
655 },
656 is_cancel,
657 reason: reason.map(str::to_string),
658 };
659
660 assert_eq!(delta.is_fill_driven_cancel(), expected);
661 }
662
663 #[rstest]
664 fn test_deserialize_open_orders_cancel_from_fixture() {
665 let json = include_str!("../../../test_data/ws_futures_open_orders_cancel.json");
666 let cancel: KrakenFuturesOpenOrdersCancel = serde_json::from_str(json).unwrap();
667
668 assert_eq!(cancel.feed, KrakenFuturesFeed::OpenOrders);
669 assert!(cancel.is_cancel);
670 assert_eq!(cancel.order_id, "660c6b23-8007-48c1-a7c9-4893f4572e8c");
671 assert_eq!(cancel.reason, Some("cancelled_by_user".to_string()));
672 assert!(cancel.cli_ord_id.is_none()); }
674
675 #[rstest]
676 fn test_deserialize_fills_snapshot_from_fixture() {
677 let json = include_str!("../../../test_data/ws_futures_fills_snapshot.json");
678 let snapshot: KrakenFuturesFillsSnapshot = serde_json::from_str(json).unwrap();
679
680 assert_eq!(snapshot.feed, KrakenFuturesFeed::FillsSnapshot);
681 assert_eq!(snapshot.fills.len(), 2);
682 assert_eq!(
683 snapshot.fills[0].instrument,
684 Some(Ustr::from("FI_XBTUSD_200925"))
685 );
686 assert!(snapshot.fills[0].buy);
687 assert_eq!(snapshot.fills[0].fill_type, KrakenFillType::Maker);
688 }
689
690 #[rstest]
691 fn test_classify_ticker_message() {
692 let json = include_str!("../../../test_data/ws_futures_ticker.json");
693 let value: Value = serde_json::from_str(json).unwrap();
694 assert_eq!(
695 classify_futures_message(&value),
696 KrakenFuturesMessageType::Ticker
697 );
698 }
699
700 #[rstest]
701 fn test_classify_trade_message() {
702 let json = include_str!("../../../test_data/ws_futures_trade.json");
703 let value: Value = serde_json::from_str(json).unwrap();
704 assert_eq!(
705 classify_futures_message(&value),
706 KrakenFuturesMessageType::Trade
707 );
708 }
709
710 #[rstest]
711 fn test_classify_trade_snapshot_message() {
712 let json = include_str!("../../../test_data/ws_futures_trade_snapshot.json");
713 let value: Value = serde_json::from_str(json).unwrap();
714 assert_eq!(
715 classify_futures_message(&value),
716 KrakenFuturesMessageType::TradeSnapshot
717 );
718 }
719
720 #[rstest]
721 fn test_classify_book_snapshot_message() {
722 let json = include_str!("../../../test_data/ws_futures_book_snapshot.json");
723 let value: Value = serde_json::from_str(json).unwrap();
724 assert_eq!(
725 classify_futures_message(&value),
726 KrakenFuturesMessageType::BookSnapshot
727 );
728 }
729
730 #[rstest]
731 fn test_classify_book_delta_message() {
732 let json = include_str!("../../../test_data/ws_futures_book_delta.json");
733 let value: Value = serde_json::from_str(json).unwrap();
734 assert_eq!(
735 classify_futures_message(&value),
736 KrakenFuturesMessageType::BookDelta
737 );
738 }
739
740 #[rstest]
741 fn test_classify_open_orders_delta_message() {
742 let json = include_str!("../../../test_data/ws_futures_open_orders_delta.json");
743 let value: Value = serde_json::from_str(json).unwrap();
744 assert_eq!(
745 classify_futures_message(&value),
746 KrakenFuturesMessageType::OpenOrdersDelta
747 );
748 }
749
750 #[rstest]
751 fn test_classify_open_orders_cancel_message() {
752 let json = include_str!("../../../test_data/ws_futures_open_orders_cancel.json");
753 let value: Value = serde_json::from_str(json).unwrap();
754 assert_eq!(
755 classify_futures_message(&value),
756 KrakenFuturesMessageType::OpenOrdersCancel
757 );
758 }
759
760 #[rstest]
761 fn test_classify_heartbeat_message() {
762 let json = r#"{"feed":"heartbeat","time":1700000000000}"#;
763 let value: Value = serde_json::from_str(json).unwrap();
764 assert_eq!(
765 classify_futures_message(&value),
766 KrakenFuturesMessageType::Heartbeat
767 );
768 }
769
770 #[rstest]
771 fn test_classify_info_event() {
772 let json = r#"{"event":"info","version":1}"#;
773 let value: Value = serde_json::from_str(json).unwrap();
774 assert_eq!(
775 classify_futures_message(&value),
776 KrakenFuturesMessageType::Info
777 );
778 }
779
780 #[rstest]
781 fn test_classify_subscribed_event() {
782 let json = r#"{"event":"subscribed","feed":"ticker","product_ids":["PI_XBTUSD"]}"#;
783 let value: Value = serde_json::from_str(json).unwrap();
784 assert_eq!(
785 classify_futures_message(&value),
786 KrakenFuturesMessageType::Subscribed
787 );
788 }
789
790 #[rstest]
791 fn test_classify_error_event() {
792 let json = r#"{"event":"error","message":"Unknown product_id"}"#;
793 let value: Value = serde_json::from_str(json).unwrap();
794 assert_eq!(
795 classify_futures_message(&value),
796 KrakenFuturesMessageType::Error
797 );
798 }
799
800 #[rstest]
801 fn test_classify_alert_event() {
802 let json = r#"{"event":"alert","message":"Rate limit exceeded"}"#;
803 let value: Value = serde_json::from_str(json).unwrap();
804 assert_eq!(
805 classify_futures_message(&value),
806 KrakenFuturesMessageType::Alert
807 );
808 }
809}