1use ahash::AHashMap;
19use serde::{Deserialize, Serialize};
20
21use crate::common::enums::{
22 KrakenApiResult, KrakenFillType, KrakenFuturesOrderEventType, KrakenFuturesOrderStatus,
23 KrakenFuturesOrderType, KrakenInstrumentType, KrakenOrderSide, KrakenPositionSide,
24 KrakenSendStatus, KrakenTriggerSide, KrakenTriggerSignal,
25};
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct FuturesMarginLevel {
32 #[serde(alias = "numNonContractUnits", default)]
35 pub contracts: f64,
36 pub initial_margin: f64,
37 pub maintenance_margin: f64,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct FuturesInstrument {
43 pub symbol: String,
44 #[serde(rename = "type")]
45 pub instrument_type: KrakenInstrumentType,
46 #[serde(default)]
48 pub underlying: Option<String>,
49 pub tick_size: f64,
50 pub contract_size: f64,
51 pub tradeable: bool,
52 #[serde(default)]
53 pub impact_mid_size: Option<f64>,
54 #[serde(default)]
55 pub max_position_size: Option<f64>,
56 pub opening_date: String,
57 pub margin_levels: Vec<FuturesMarginLevel>,
58 #[serde(default)]
59 pub funding_rate_coefficient: Option<i32>,
60 #[serde(default)]
61 pub max_relative_funding_rate: Option<f64>,
62 #[serde(default)]
63 pub isin: Option<String>,
64 pub contract_value_trade_precision: i32,
65 pub post_only: bool,
66 #[serde(default)]
67 pub fee_schedule_uid: Option<String>,
68 pub mtf: bool,
69 pub base: String,
70 pub quote: String,
71 pub pair: String,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct FuturesInstrumentsResponse {
76 pub result: KrakenApiResult,
77 pub instruments: Vec<FuturesInstrument>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct FuturesTicker {
85 pub symbol: String,
86 #[serde(default)]
87 pub last: Option<f64>,
88 #[serde(default)]
89 pub last_time: Option<String>,
90 pub tag: String,
91 pub pair: String,
92 #[serde(default)]
93 pub mark_price: Option<f64>,
94 #[serde(default)]
95 pub bid: Option<f64>,
96 #[serde(default)]
97 pub bid_size: Option<f64>,
98 #[serde(default)]
99 pub ask: Option<f64>,
100 #[serde(default)]
101 pub ask_size: Option<f64>,
102 #[serde(rename = "vol24h", default)]
103 pub vol_24h: Option<f64>,
104 #[serde(default)]
105 pub volume_quote: Option<f64>,
106 #[serde(default)]
107 pub open_interest: Option<f64>,
108 #[serde(rename = "open24h", default)]
109 pub open_24h: Option<f64>,
110 #[serde(rename = "high24h", default)]
111 pub high_24h: Option<f64>,
112 #[serde(rename = "low24h", default)]
113 pub low_24h: Option<f64>,
114 #[serde(default)]
115 pub last_size: Option<f64>,
116 #[serde(default)]
117 pub funding_rate: Option<f64>,
118 #[serde(default)]
119 pub funding_rate_prediction: Option<f64>,
120 #[serde(default)]
121 pub suspended: bool,
122 #[serde(default)]
123 pub index_price: Option<f64>,
124 #[serde(default)]
125 pub post_only: bool,
126 #[serde(rename = "change24h", default)]
127 pub change_24h: Option<f64>,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct FuturesTickersResponse {
133 pub result: KrakenApiResult,
134 #[serde(default)]
135 pub server_time: Option<String>,
136 pub tickers: Vec<FuturesTicker>,
137}
138
139#[derive(Debug, Clone, Serialize)]
143pub struct FuturesOrderBookLevel {
144 pub price: f64,
145 pub qty: f64,
146}
147
148impl<'de> serde::Deserialize<'de> for FuturesOrderBookLevel {
149 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
150 where
151 D: serde::Deserializer<'de>,
152 {
153 let arr: (f64, f64) = serde::Deserialize::deserialize(deserializer)?;
154 Ok(Self {
155 price: arr.0,
156 qty: arr.1,
157 })
158 }
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(rename_all = "camelCase")]
163pub struct FuturesOrderBookData {
164 pub bids: Vec<FuturesOrderBookLevel>,
165 pub asks: Vec<FuturesOrderBookLevel>,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct FuturesOrderBookResponse {
171 pub result: KrakenApiResult,
172 pub order_book: FuturesOrderBookData,
173 #[serde(default)]
174 pub server_time: Option<String>,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
180#[serde(rename_all = "camelCase")]
181pub struct FuturesHistoricalFundingRate {
182 pub timestamp: String,
183 pub relative_funding_rate: f64,
184 pub funding_rate: f64,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
188#[serde(rename_all = "camelCase")]
189pub struct FuturesHistoricalFundingRatesResponse {
190 pub result: KrakenApiResult,
191 pub rates: Vec<FuturesHistoricalFundingRate>,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct FuturesCandle {
198 pub time: i64,
199 pub open: String,
200 pub high: String,
201 pub low: String,
202 pub close: String,
203 pub volume: String,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct FuturesCandlesResponse {
208 pub candles: Vec<FuturesCandle>,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
214#[serde(rename_all = "camelCase")]
215pub struct FuturesOpenOrder {
216 #[serde(rename = "order_id")]
217 pub order_id: String,
218 pub symbol: String,
219 pub side: KrakenOrderSide,
220 pub order_type: KrakenFuturesOrderType,
221 #[serde(default)]
222 pub limit_price: Option<f64>,
223 #[serde(default)]
224 pub stop_price: Option<f64>,
225 pub unfilled_size: f64,
226 pub received_time: String,
227 pub status: KrakenFuturesOrderStatus,
228 pub filled_size: f64,
229 #[serde(default)]
230 pub reduce_only: Option<bool>,
231 pub last_update_time: String,
232 #[serde(default)]
233 pub trigger_signal: Option<KrakenTriggerSignal>,
234 #[serde(rename = "cli_ord_id", default)]
235 pub cli_ord_id: Option<String>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
239#[serde(rename_all = "camelCase")]
240pub struct FuturesOpenOrdersResponse {
241 pub result: KrakenApiResult,
242 #[serde(default)]
243 pub server_time: Option<String>,
244 #[serde(default)]
245 pub error: Option<String>,
246 #[serde(default)]
247 pub open_orders: Vec<FuturesOpenOrder>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
254#[serde(rename_all = "camelCase")]
255pub struct FuturesOrderEventWrapper {
256 pub order: FuturesOrderEvent,
257 #[serde(rename = "type")]
258 pub event_type: KrakenFuturesOrderEventType,
259 #[serde(default)]
260 pub reduced_quantity: Option<f64>,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct FuturesOrderEvent {
267 pub order_id: String,
268 #[serde(default)]
269 pub cli_ord_id: Option<String>,
270 #[serde(rename = "type")]
271 pub order_type: KrakenFuturesOrderType,
272 pub symbol: String,
273 pub side: KrakenOrderSide,
274 pub quantity: f64,
275 pub filled: f64,
276 #[serde(default)]
277 pub limit_price: Option<f64>,
278 #[serde(default)]
279 pub stop_price: Option<f64>,
280 pub timestamp: String,
281 pub last_update_timestamp: String,
282 #[serde(default)]
283 pub reduce_only: bool,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289pub struct FuturesOrderEventsResponse {
290 #[serde(default)]
291 pub server_time: Option<String>,
292 #[serde(default)]
293 pub order_events: Vec<FuturesOrderEventWrapper>,
294 #[serde(default)]
295 pub continuation_token: Option<String>,
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct FuturesFill {
303 #[serde(rename = "fill_id")]
304 pub fill_id: String,
305 pub symbol: String,
306 pub side: KrakenOrderSide,
307 #[serde(rename = "order_id")]
308 pub order_id: String,
309 pub fill_time: String,
310 pub size: f64,
311 pub price: f64,
312 pub fill_type: KrakenFillType,
313 #[serde(rename = "cli_ord_id", default)]
314 pub cli_ord_id: Option<String>,
315 #[serde(rename = "fee_paid", default)]
316 pub fee_paid: Option<f64>,
317 #[serde(rename = "fee_currency", default)]
318 pub fee_currency: Option<String>,
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
322#[serde(rename_all = "camelCase")]
323pub struct FuturesFillsResponse {
324 pub result: KrakenApiResult,
325 #[serde(default)]
326 pub server_time: Option<String>,
327 #[serde(default)]
328 pub error: Option<String>,
329 #[serde(default)]
330 pub fills: Vec<FuturesFill>,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
336#[serde(rename_all = "camelCase")]
337pub struct FuturesPosition {
338 pub side: KrakenPositionSide,
339 pub symbol: String,
340 pub price: f64,
341 pub fill_time: String,
342 pub size: f64,
343 #[serde(default)]
344 pub unrealized_funding: Option<f64>,
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize)]
348#[serde(rename_all = "camelCase")]
349pub struct FuturesOpenPositionsResponse {
350 pub result: KrakenApiResult,
351 #[serde(default)]
352 pub server_time: Option<String>,
353 #[serde(default)]
354 pub error: Option<String>,
355 #[serde(default)]
356 pub open_positions: Vec<FuturesPosition>,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
362#[serde(rename_all = "camelCase")]
363pub struct FuturesSendOrderResponse {
364 pub result: KrakenApiResult,
365 #[serde(default)]
366 pub server_time: Option<String>,
367 #[serde(default)]
368 pub error: Option<String>,
369 pub send_status: Option<FuturesSendStatus>,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
373#[serde(rename_all = "camelCase")]
374pub struct FuturesSendStatus {
375 #[serde(rename = "order_id", default)]
376 pub order_id: Option<String>,
377 pub status: String,
378 #[serde(default)]
379 pub order_events: Option<Vec<FuturesSendOrderEvent>>,
380 #[serde(rename = "cli_ord_id", default)]
381 pub cli_ord_id: Option<String>,
382 #[serde(rename = "receivedTime", default)]
383 pub received_time: Option<String>,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
387#[serde(rename_all = "camelCase")]
388pub struct FuturesSendOrderEvent {
389 #[serde(rename = "type")]
390 pub event_type: KrakenFuturesOrderEventType,
391 #[serde(default)]
392 pub order: Option<FuturesOrderEventData>,
393 #[serde(default)]
394 pub order_trigger: Option<FuturesOrderTriggerData>,
395 #[serde(default)]
396 pub reduced_quantity: Option<f64>,
397 #[serde(rename = "executionId", default)]
399 pub execution_id: Option<String>,
400 #[serde(default)]
401 pub price: Option<f64>,
402 #[serde(default)]
403 pub amount: Option<f64>,
404 #[serde(rename = "orderPriorEdit", default)]
405 pub order_prior_edit: Option<Box<FuturesOrderEventData>>,
406 #[serde(rename = "orderPriorExecution", default)]
407 pub order_prior_execution: Option<Box<FuturesOrderEventData>>,
408 #[serde(rename = "takerReducedQuantity", default)]
409 pub taker_reduced_quantity: Option<f64>,
410 #[serde(default)]
412 pub reason: Option<String>,
413 #[serde(default)]
414 pub uid: Option<String>,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
418#[serde(rename_all = "camelCase")]
419pub struct FuturesOrderEventData {
420 #[serde(rename = "orderId")]
421 pub order_id: String,
422 #[serde(rename = "cliOrdId", default)]
423 pub cli_ord_id: Option<String>,
424 #[serde(rename = "type")]
425 pub order_type: KrakenFuturesOrderType,
426 pub symbol: String,
427 pub side: KrakenOrderSide,
428 pub quantity: f64,
429 pub filled: f64,
430 #[serde(rename = "limitPrice", default)]
431 pub limit_price: Option<f64>,
432 #[serde(rename = "stopPrice", default)]
433 pub stop_price: Option<f64>,
434 pub timestamp: String,
435 #[serde(rename = "lastUpdateTimestamp")]
436 pub last_update_timestamp: String,
437 #[serde(rename = "reduceOnly", default)]
438 pub reduce_only: bool,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize)]
442#[serde(rename_all = "camelCase")]
443pub struct FuturesOrderTriggerData {
444 pub uid: String,
445 #[serde(rename = "clientId", default)]
446 pub client_id: Option<String>,
447 #[serde(rename = "type")]
448 pub order_type: KrakenFuturesOrderType,
449 pub symbol: String,
450 pub side: KrakenOrderSide,
451 pub quantity: f64,
452 #[serde(rename = "limitPrice", default)]
453 pub limit_price: Option<f64>,
454 #[serde(rename = "limitPriceOffsetValue", default)]
455 pub limit_price_offset_value: Option<f64>,
456 #[serde(rename = "limitPriceOffsetUnit", default)]
457 pub limit_price_offset_unit: Option<String>,
458 #[serde(rename = "triggerPrice")]
459 pub trigger_price: f64,
460 #[serde(rename = "triggerSide")]
461 pub trigger_side: KrakenTriggerSide,
462 #[serde(rename = "triggerSignal")]
463 pub trigger_signal: KrakenTriggerSignal,
464 #[serde(rename = "reduceOnly", default)]
465 pub reduce_only: bool,
466 pub timestamp: String,
467 #[serde(rename = "lastUpdateTimestamp")]
468 pub last_update_timestamp: String,
469}
470
471#[derive(Debug, Clone, Serialize, Deserialize)]
472#[serde(rename_all = "camelCase")]
473pub struct FuturesCancelOrderResponse {
474 pub result: KrakenApiResult,
475 #[serde(default)]
476 pub server_time: Option<String>,
477 pub cancel_status: FuturesCancelStatus,
478}
479
480#[derive(Debug, Clone, Serialize, Deserialize)]
481#[serde(rename_all = "camelCase")]
482pub struct FuturesCancelStatus {
483 pub status: KrakenSendStatus,
484 #[serde(rename = "order_id", default)]
485 pub order_id: Option<String>,
486 #[serde(rename = "cli_ord_id", default)]
487 pub cli_ord_id: Option<String>,
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize)]
491#[serde(rename_all = "camelCase")]
492pub struct FuturesEditOrderResponse {
493 pub result: KrakenApiResult,
494 #[serde(default)]
495 pub server_time: Option<String>,
496 pub edit_status: FuturesEditStatus,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
500#[serde(rename_all = "camelCase")]
501pub struct FuturesEditStatus {
502 pub status: String,
503 #[serde(rename = "order_id", default)]
504 pub order_id: Option<String>,
505 #[serde(rename = "cli_ord_id", default)]
506 pub cli_ord_id: Option<String>,
507}
508
509#[derive(Debug, Clone, Serialize, Deserialize)]
510#[serde(rename_all = "camelCase")]
511pub struct FuturesBatchOrderResponse {
512 pub result: KrakenApiResult,
513 #[serde(default)]
514 pub server_time: Option<String>,
515 pub batch_status: Vec<FuturesSendStatus>,
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize)]
523#[serde(rename_all = "camelCase")]
524pub struct FuturesBatchCancelResponse {
525 pub result: KrakenApiResult,
526 #[serde(default)]
527 pub server_time: Option<String>,
528 #[serde(default)]
529 pub error: Option<String>,
530 #[serde(default)]
531 pub batch_status: Vec<FuturesBatchCancelStatus>,
532}
533
534#[derive(Debug, Clone, Serialize, Deserialize)]
535#[serde(rename_all = "camelCase")]
536pub struct FuturesBatchCancelStatus {
537 #[serde(default)]
538 pub order_id: Option<String>,
539 #[serde(default)]
540 pub cli_ord_id: Option<String>,
541 #[serde(default)]
542 pub status: Option<KrakenSendStatus>,
543 #[serde(default)]
544 pub cancel_status: Option<FuturesCancelStatus>,
545}
546
547#[derive(Debug, Clone, Serialize, Deserialize)]
548#[serde(rename_all = "camelCase")]
549pub struct FuturesCancelAllOrdersResponse {
550 pub result: KrakenApiResult,
551 #[serde(default)]
552 pub server_time: Option<String>,
553 pub cancel_status: FuturesCancelAllStatus,
554}
555
556#[derive(Debug, Clone, Serialize, Deserialize)]
557#[serde(rename_all = "camelCase")]
558pub struct FuturesCancelAllStatus {
559 pub status: KrakenSendStatus,
560 #[serde(default)]
561 pub cancelled_orders: Vec<CancelledOrder>,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
565#[serde(rename_all = "camelCase")]
566pub struct CancelledOrder {
567 #[serde(rename = "order_id", default)]
568 pub order_id: Option<String>,
569 #[serde(default)]
570 pub cli_ord_id: Option<String>,
571}
572
573#[derive(Debug, Clone, Serialize, Deserialize)]
577#[serde(rename_all = "camelCase")]
578pub struct FuturesPublicExecutionsResponse {
579 pub elements: Vec<FuturesPublicExecutionElement>,
580 #[serde(default)]
581 pub len: Option<i64>,
582 #[serde(default)]
583 pub continuation_token: Option<String>,
584}
585
586#[derive(Debug, Clone, Serialize, Deserialize)]
588pub struct FuturesPublicExecutionElement {
589 pub uid: String,
590 pub timestamp: i64,
591 pub event: FuturesPublicExecutionEvent,
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize)]
596pub struct FuturesPublicExecutionEvent {
597 #[serde(rename = "Execution")]
598 pub execution: FuturesPublicExecutionWrapper,
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
603#[serde(rename_all = "camelCase")]
604pub struct FuturesPublicExecutionWrapper {
605 pub execution: FuturesPublicExecution,
606 #[serde(default)]
607 pub taker_reduced_quantity: Option<String>,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize)]
612#[serde(rename_all = "camelCase")]
613pub struct FuturesPublicExecution {
614 pub uid: String,
615 pub maker_order: FuturesPublicOrder,
616 pub taker_order: FuturesPublicOrder,
617 pub timestamp: i64,
618 pub quantity: String,
619 pub price: String,
620 #[serde(default)]
621 pub mark_price: Option<String>,
622 #[serde(default)]
623 pub limit_filled: Option<bool>,
624 #[serde(default)]
625 pub usd_value: Option<String>,
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize)]
630#[serde(rename_all = "camelCase")]
631pub struct FuturesPublicOrder {
632 pub uid: String,
633 pub tradeable: String,
634 pub direction: String,
635 pub quantity: String,
636 pub timestamp: i64,
637 #[serde(default)]
638 pub limit_price: Option<String>,
639 #[serde(default)]
640 pub order_type: Option<String>,
641 #[serde(default)]
642 pub reduce_only: Option<bool>,
643 #[serde(default)]
644 pub last_update_timestamp: Option<i64>,
645}
646
647#[derive(Debug, Clone, Serialize, Deserialize)]
651#[serde(rename_all = "camelCase")]
652pub struct FuturesAccountsResponse {
653 pub result: KrakenApiResult,
654 #[serde(default)]
655 pub accounts: AHashMap<String, FuturesAccount>,
656 #[serde(default)]
657 pub error: Option<String>,
658 #[serde(default)]
659 pub server_time: Option<String>,
660}
661
662#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
664#[serde(rename_all = "camelCase")]
665pub enum KrakenFuturesAccountType {
666 MultiCollateralMarginAccount,
668 MarginAccount,
670 CashAccount,
672 #[serde(other)]
674 Unknown,
675}
676
677#[derive(Debug, Clone, Serialize, Deserialize)]
679#[serde(rename_all = "camelCase")]
680pub struct FuturesAccount {
681 #[serde(rename = "type")]
682 pub account_type: KrakenFuturesAccountType,
683 #[serde(default)]
685 pub balances: AHashMap<String, f64>,
686 #[serde(default)]
688 pub currencies: AHashMap<String, FuturesFlexCurrency>,
689 #[serde(default)]
691 pub auxiliary: Option<FuturesAuxiliary>,
692 #[serde(default)]
694 pub margin_requirements: Option<FuturesMarginRequirements>,
695 #[serde(default)]
697 pub portfolio_value: Option<f64>,
698 #[serde(default)]
700 pub available_margin: Option<f64>,
701 #[serde(default)]
703 pub initial_margin: Option<f64>,
704 #[serde(default)]
706 pub pnl: Option<f64>,
707}
708
709#[derive(Debug, Clone, Serialize, Deserialize)]
711#[serde(rename_all = "camelCase")]
712pub struct FuturesFlexCurrency {
713 pub quantity: f64,
714 #[serde(default)]
715 pub value: Option<f64>,
716 #[serde(default)]
717 pub collateral: Option<f64>,
718 #[serde(default)]
719 pub available: Option<f64>,
720}
721
722#[derive(Debug, Clone, Serialize, Deserialize)]
724#[serde(rename_all = "camelCase")]
725pub struct FuturesAuxiliary {
726 #[serde(default)]
727 pub usd: Option<f64>,
728 #[serde(default)]
730 pub pv: Option<f64>,
731 #[serde(default)]
733 pub pnl: Option<f64>,
734 #[serde(default)]
736 pub af: Option<f64>,
737 #[serde(default)]
738 pub funding: Option<f64>,
739}
740
741#[derive(Debug, Clone, Serialize, Deserialize)]
743#[serde(rename_all = "camelCase")]
744pub struct FuturesMarginRequirements {
745 #[serde(default)]
747 pub im: Option<f64>,
748 #[serde(default)]
750 pub mm: Option<f64>,
751 #[serde(default)]
753 pub lt: Option<f64>,
754 #[serde(default)]
756 pub tt: Option<f64>,
757}
758
759#[cfg(test)]
760mod tests {
761 use rstest::rstest;
762
763 use super::*;
764
765 fn load_test_data(filename: &str) -> String {
766 let path = format!("test_data/{filename}");
767 std::fs::read_to_string(&path)
768 .unwrap_or_else(|e| panic!("Failed to load test data from {path}: {e}"))
769 }
770
771 #[rstest]
772 fn test_parse_futures_cancel_all_orders_with_no_orders_to_cancel_status() {
773 let raw = r#"{
778 "result": "success",
779 "cancelStatus": {
780 "receivedTime": "2026-04-10T13:17:23.291Z",
781 "cancelOnly": "PF_XBTUSD",
782 "status": "noOrdersToCancel",
783 "cancelledOrders": [
784 {
785 "order_id": "a182b1c0-cd01-4d1c-853b-605e936f412b",
786 "cliOrdId": "5f173994-f660-4809-b97a-586221fe5926"
787 }
788 ],
789 "orderEvents": []
790 },
791 "serverTime": "2026-04-10T13:17:23.291Z"
792 }"#;
793
794 let response: FuturesCancelAllOrdersResponse =
795 serde_json::from_str(raw).expect("Failed to parse cancel-all response");
796
797 assert_eq!(response.result, KrakenApiResult::Success);
798 assert_eq!(
799 response.cancel_status.status,
800 KrakenSendStatus::NoOrdersToCancel
801 );
802 assert_eq!(response.cancel_status.cancelled_orders.len(), 1);
803 assert_eq!(
804 response.cancel_status.cancelled_orders[0]
805 .order_id
806 .as_deref(),
807 Some("a182b1c0-cd01-4d1c-853b-605e936f412b")
808 );
809 assert_eq!(
810 response.cancel_status.cancelled_orders[0]
811 .cli_ord_id
812 .as_deref(),
813 Some("5f173994-f660-4809-b97a-586221fe5926")
814 );
815 }
816
817 #[rstest]
818 fn test_parse_futures_open_orders() {
819 let data = load_test_data("http_futures_open_orders.json");
820 let response: FuturesOpenOrdersResponse =
821 serde_json::from_str(&data).expect("Failed to parse futures open orders");
822
823 assert_eq!(response.result, KrakenApiResult::Success);
824 assert_eq!(response.open_orders.len(), 3);
825
826 let order = &response.open_orders[0];
827 assert_eq!(order.order_id, "2ce038ae-c144-4de7-a0f1-82f7f4fca864");
828 assert_eq!(order.symbol, "PI_ETHUSD");
829 assert_eq!(order.side, KrakenOrderSide::Buy);
830 assert_eq!(order.order_type, KrakenFuturesOrderType::Limit);
831 assert_eq!(order.limit_price, Some(1200.0));
832 assert_eq!(order.unfilled_size, 100.0);
833 assert_eq!(order.filled_size, 0.0);
834 }
835
836 #[rstest]
837 fn test_parse_futures_fills() {
838 let data = load_test_data("http_futures_fills.json");
839 let response: FuturesFillsResponse =
840 serde_json::from_str(&data).expect("Failed to parse futures fills");
841
842 assert_eq!(response.result, KrakenApiResult::Success);
843 assert_eq!(response.fills.len(), 3);
844
845 let fill = &response.fills[0];
846 assert_eq!(fill.fill_id, "cad76f07-814e-4dc6-8478-7867407b6bff");
847 assert_eq!(fill.symbol, "PI_XBTUSD");
848 assert_eq!(fill.side, KrakenOrderSide::Buy);
849 assert_eq!(fill.size, 5000.0);
850 assert_eq!(fill.price, 27937.5);
851 assert_eq!(fill.fill_type, KrakenFillType::Maker);
852 assert_eq!(fill.fee_currency, Some("BTC".to_string()));
853 }
854
855 #[rstest]
856 fn test_parse_futures_open_positions() {
857 let data = load_test_data("http_futures_open_positions.json");
858 let response: FuturesOpenPositionsResponse =
859 serde_json::from_str(&data).expect("Failed to parse futures open positions");
860
861 assert_eq!(response.result, KrakenApiResult::Success);
862 assert_eq!(response.open_positions.len(), 2);
863
864 let position = &response.open_positions[0];
865 assert_eq!(position.side, KrakenPositionSide::Short);
866 assert_eq!(position.symbol, "PI_XBTUSD");
867 assert_eq!(position.size, 8000.0);
868 assert!(position.unrealized_funding.is_some());
869 }
870
871 #[rstest]
872 fn test_parse_futures_orderbook() {
873 let data = load_test_data("http_futures_orderbook.json");
874 let response: FuturesOrderBookResponse =
875 serde_json::from_str(&data).expect("Failed to parse futures orderbook");
876
877 assert_eq!(response.result, KrakenApiResult::Success);
878 assert_eq!(response.order_book.bids.len(), 3);
879 assert_eq!(response.order_book.asks.len(), 3);
880
881 let best_bid = &response.order_book.bids[0];
882 assert_eq!(best_bid.price, 105900.0);
883 assert_eq!(best_bid.qty, 0.5);
884
885 let best_ask = &response.order_book.asks[0];
886 assert_eq!(best_ask.price, 105950.0);
887 assert_eq!(best_ask.qty, 0.3);
888 }
889
890 #[rstest]
891 fn test_parse_futures_historical_funding_rates() {
892 let data = load_test_data("http_futures_historical_funding_rates.json");
893 let response: FuturesHistoricalFundingRatesResponse =
894 serde_json::from_str(&data).expect("Failed to parse historical funding rates");
895
896 assert_eq!(response.result, KrakenApiResult::Success);
897 assert_eq!(response.rates.len(), 3);
898
899 let rate = &response.rates[0];
900 assert_eq!(rate.timestamp, "2025-07-11T08:00:00.000Z");
901 assert_eq!(rate.relative_funding_rate, 0.0001);
902 assert_eq!(rate.funding_rate, 0.00005);
903
904 let negative_rate = &response.rates[1];
905 assert_eq!(negative_rate.relative_funding_rate, -0.00005);
906 }
907
908 #[rstest]
909 fn test_parse_futures_order_events_uses_enum_event_type() {
910 let data = load_test_data("http_futures_order_events.json");
911 let response: FuturesOrderEventsResponse =
912 serde_json::from_str(&data).expect("Failed to parse futures order events");
913
914 assert_eq!(response.order_events.len(), 3);
915 assert_eq!(
916 response.order_events[0].event_type,
917 KrakenFuturesOrderEventType::Place
918 );
919 assert_eq!(
920 response.order_events[1].event_type,
921 KrakenFuturesOrderEventType::Fill
922 );
923 assert_eq!(
924 response.order_events[2].event_type,
925 KrakenFuturesOrderEventType::Cancel
926 );
927 }
928
929 #[rstest]
930 fn test_parse_futures_order_events_tolerates_unknown_enum_values() {
931 let data = load_test_data("http_futures_order_events_unknown.json");
934 let response: FuturesOrderEventsResponse =
935 serde_json::from_str(&data).expect("Failed to parse futures order events with unknown");
936
937 assert_eq!(response.order_events.len(), 1);
938 assert_eq!(
939 response.order_events[0].order.order_type,
940 KrakenFuturesOrderType::Unknown
941 );
942 }
943
944 #[rstest]
945 fn test_parse_futures_order_trigger_data_tolerates_unknown_enum_values() {
946 let data = load_test_data("http_send_order_futures_unknown_trigger.json");
949 let response: FuturesSendOrderResponse =
950 serde_json::from_str(&data).expect("Failed to parse send-order response with unknown");
951
952 let send_status = response.send_status.expect("sendStatus missing");
953 let order_events = send_status.order_events.expect("orderEvents missing");
954 let trigger = order_events[0]
955 .order_trigger
956 .as_ref()
957 .expect("orderTrigger missing");
958
959 assert_eq!(trigger.order_type, KrakenFuturesOrderType::Unknown);
960 assert_eq!(trigger.trigger_side, KrakenTriggerSide::Unknown);
961 assert_eq!(trigger.trigger_signal, KrakenTriggerSignal::Unknown);
962 }
963
964 #[rstest]
965 fn test_parse_futures_send_order_execution_event_uses_enum_event_type() {
966 let data = r#"
967 {
968 "result": "success",
969 "sendStatus": {
970 "status": "placed",
971 "orderEvents": [
972 {
973 "type": "EXECUTION",
974 "executionId": "c8a35168-8d52-4609-944f-3f32bb0d5c77",
975 "price": 35000.5,
976 "amount": 1.25,
977 "orderPriorExecution": {
978 "orderId": "c8a35168-8d52-4609-944f-3f32bb0d5c77",
979 "cliOrdId": "test-order-001",
980 "type": "lmt",
981 "symbol": "PI_XBTUSD",
982 "side": "buy",
983 "quantity": 2.0,
984 "filled": 0.0,
985 "limitPrice": 35000.5,
986 "timestamp": "2024-01-15T10:30:45.123Z",
987 "lastUpdateTimestamp": "2024-01-15T10:30:45.123Z",
988 "reduceOnly": false
989 }
990 }
991 ]
992 }
993 }
994 "#;
995 let response: FuturesSendOrderResponse =
996 serde_json::from_str(data).expect("Failed to parse futures send order response");
997
998 let send_status = response.send_status.expect("sendStatus missing");
999 let order_events = send_status.order_events.expect("orderEvents missing");
1000
1001 assert_eq!(order_events.len(), 1);
1002 assert_eq!(
1003 order_events[0].event_type,
1004 KrakenFuturesOrderEventType::Execution
1005 );
1006 }
1007}