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