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