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