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