nautilus_kraken/http/futures/
models.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Data models for Kraken Futures HTTP API responses.
17
18use serde::{Deserialize, Serialize};
19
20use crate::common::enums::{
21    KrakenApiResult, KrakenFillType, KrakenFuturesOrderEventType, KrakenFuturesOrderStatus,
22    KrakenFuturesOrderType, KrakenInstrumentType, KrakenOrderSide, KrakenPositionSide,
23    KrakenSendStatus, KrakenTriggerSide, KrakenTriggerSignal,
24};
25
26// Futures Instruments Models
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct FuturesMarginLevel {
31    /// Number of contracts (for inverse futures) or notional units (for flexible futures).
32    /// The field name varies: `contracts` for inverse, `numNonContractUnits` for flexible.
33    #[serde(alias = "numNonContractUnits", default)]
34    pub contracts: f64,
35    pub initial_margin: f64,
36    pub maintenance_margin: f64,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct FuturesInstrument {
42    pub symbol: String,
43    #[serde(rename = "type")]
44    pub instrument_type: KrakenInstrumentType,
45    /// Only present for inverse futures, not for flexible futures.
46    #[serde(default)]
47    pub underlying: Option<String>,
48    pub tick_size: f64,
49    pub contract_size: f64,
50    pub tradeable: bool,
51    #[serde(default)]
52    pub impact_mid_size: Option<f64>,
53    #[serde(default)]
54    pub max_position_size: Option<f64>,
55    pub opening_date: String,
56    pub margin_levels: Vec<FuturesMarginLevel>,
57    #[serde(default)]
58    pub funding_rate_coefficient: Option<i32>,
59    #[serde(default)]
60    pub max_relative_funding_rate: Option<f64>,
61    #[serde(default)]
62    pub isin: Option<String>,
63    pub contract_value_trade_precision: i32,
64    pub post_only: bool,
65    pub fee_schedule_uid: String,
66    pub mtf: bool,
67    pub base: String,
68    pub quote: String,
69    pub pair: String,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct FuturesInstrumentsResponse {
74    pub result: KrakenApiResult,
75    pub instruments: Vec<FuturesInstrument>,
76}
77
78// Futures Ticker Models
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81#[serde(rename_all = "camelCase")]
82pub struct FuturesTicker {
83    pub symbol: String,
84    #[serde(default)]
85    pub last: Option<f64>,
86    #[serde(default)]
87    pub last_time: Option<String>,
88    pub tag: String,
89    pub pair: String,
90    #[serde(default)]
91    pub mark_price: Option<f64>,
92    #[serde(default)]
93    pub bid: Option<f64>,
94    #[serde(default)]
95    pub bid_size: Option<f64>,
96    #[serde(default)]
97    pub ask: Option<f64>,
98    #[serde(default)]
99    pub ask_size: Option<f64>,
100    #[serde(rename = "vol24h", default)]
101    pub vol_24h: Option<f64>,
102    #[serde(default)]
103    pub volume_quote: Option<f64>,
104    #[serde(default)]
105    pub open_interest: Option<f64>,
106    #[serde(rename = "open24h", default)]
107    pub open_24h: Option<f64>,
108    #[serde(rename = "high24h", default)]
109    pub high_24h: Option<f64>,
110    #[serde(rename = "low24h", default)]
111    pub low_24h: Option<f64>,
112    #[serde(default)]
113    pub last_size: Option<f64>,
114    #[serde(default)]
115    pub funding_rate: Option<f64>,
116    #[serde(default)]
117    pub funding_rate_prediction: Option<f64>,
118    #[serde(default)]
119    pub suspended: bool,
120    #[serde(default)]
121    pub index_price: Option<f64>,
122    #[serde(default)]
123    pub post_only: bool,
124    #[serde(rename = "change24h", default)]
125    pub change_24h: Option<f64>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129#[serde(rename_all = "camelCase")]
130pub struct FuturesTickersResponse {
131    pub result: KrakenApiResult,
132    #[serde(default)]
133    pub server_time: Option<String>,
134    pub tickers: Vec<FuturesTicker>,
135}
136
137// Futures OHLC (Candles) Models
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct FuturesCandle {
141    pub time: i64,
142    pub open: String,
143    pub high: String,
144    pub low: String,
145    pub close: String,
146    pub volume: String,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct FuturesCandlesResponse {
151    pub candles: Vec<FuturesCandle>,
152}
153
154// Futures Open Orders Models
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct FuturesOpenOrder {
159    #[serde(rename = "order_id")]
160    pub order_id: String,
161    pub symbol: String,
162    pub side: KrakenOrderSide,
163    pub order_type: KrakenFuturesOrderType,
164    #[serde(default)]
165    pub limit_price: Option<f64>,
166    #[serde(default)]
167    pub stop_price: Option<f64>,
168    pub unfilled_size: f64,
169    pub received_time: String,
170    pub status: KrakenFuturesOrderStatus,
171    pub filled_size: f64,
172    #[serde(default)]
173    pub reduce_only: Option<bool>,
174    pub last_update_time: String,
175    #[serde(default)]
176    pub trigger_signal: Option<KrakenTriggerSignal>,
177    #[serde(rename = "cli_ord_id", default)]
178    pub cli_ord_id: Option<String>,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct FuturesOpenOrdersResponse {
184    pub result: KrakenApiResult,
185    #[serde(default)]
186    pub server_time: Option<String>,
187    #[serde(default)]
188    pub error: Option<String>,
189    #[serde(default)]
190    pub open_orders: Vec<FuturesOpenOrder>,
191}
192
193// Futures Order Events Models (v2 API)
194
195/// Wrapper for an order event containing the order data and event type.
196#[derive(Debug, Clone, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct FuturesOrderEventWrapper {
199    pub order: FuturesOrderEvent,
200    #[serde(rename = "type")]
201    pub event_type: String,
202    #[serde(default)]
203    pub reduced_quantity: Option<f64>,
204}
205
206/// The actual order data within an order event.
207#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(rename_all = "camelCase")]
209pub struct FuturesOrderEvent {
210    pub order_id: String,
211    #[serde(default)]
212    pub cli_ord_id: Option<String>,
213    #[serde(rename = "type")]
214    pub order_type: KrakenFuturesOrderType,
215    pub symbol: String,
216    pub side: KrakenOrderSide,
217    pub quantity: f64,
218    pub filled: f64,
219    #[serde(default)]
220    pub limit_price: Option<f64>,
221    #[serde(default)]
222    pub stop_price: Option<f64>,
223    pub timestamp: String,
224    pub last_update_timestamp: String,
225    #[serde(default)]
226    pub reduce_only: bool,
227}
228
229/// Response from the Kraken Futures order events v2 endpoint.
230#[derive(Debug, Clone, Serialize, Deserialize)]
231#[serde(rename_all = "camelCase")]
232pub struct FuturesOrderEventsResponse {
233    #[serde(default)]
234    pub server_time: Option<String>,
235    #[serde(default)]
236    pub order_events: Vec<FuturesOrderEventWrapper>,
237    #[serde(default)]
238    pub continuation_token: Option<String>,
239}
240
241// Futures Fills Models
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
244#[serde(rename_all = "camelCase")]
245pub struct FuturesFill {
246    #[serde(rename = "fill_id")]
247    pub fill_id: String,
248    pub symbol: String,
249    pub side: KrakenOrderSide,
250    #[serde(rename = "order_id")]
251    pub order_id: String,
252    pub fill_time: String,
253    pub size: f64,
254    pub price: f64,
255    pub fill_type: KrakenFillType,
256    #[serde(rename = "cli_ord_id", default)]
257    pub cli_ord_id: Option<String>,
258    #[serde(rename = "fee_paid", default)]
259    pub fee_paid: Option<f64>,
260    #[serde(rename = "fee_currency", default)]
261    pub fee_currency: Option<String>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct FuturesFillsResponse {
267    pub result: KrakenApiResult,
268    #[serde(default)]
269    pub server_time: Option<String>,
270    #[serde(default)]
271    pub error: Option<String>,
272    #[serde(default)]
273    pub fills: Vec<FuturesFill>,
274}
275
276// Futures Positions Models
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct FuturesPosition {
281    pub side: KrakenPositionSide,
282    pub symbol: String,
283    pub price: f64,
284    pub fill_time: String,
285    pub size: f64,
286    #[serde(default)]
287    pub unrealized_funding: Option<f64>,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
291#[serde(rename_all = "camelCase")]
292pub struct FuturesOpenPositionsResponse {
293    pub result: KrakenApiResult,
294    #[serde(default)]
295    pub server_time: Option<String>,
296    #[serde(default)]
297    pub error: Option<String>,
298    #[serde(default)]
299    pub open_positions: Vec<FuturesPosition>,
300}
301
302// Futures Order Execution Models
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
305#[serde(rename_all = "camelCase")]
306pub struct FuturesSendOrderResponse {
307    pub result: KrakenApiResult,
308    #[serde(default)]
309    pub server_time: Option<String>,
310    #[serde(default)]
311    pub error: Option<String>,
312    pub send_status: Option<FuturesSendStatus>,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
316#[serde(rename_all = "camelCase")]
317pub struct FuturesSendStatus {
318    #[serde(rename = "order_id", default)]
319    pub order_id: Option<String>,
320    pub status: String,
321    #[serde(default)]
322    pub order_events: Option<Vec<FuturesSendOrderEvent>>,
323    #[serde(rename = "cli_ord_id", default)]
324    pub cli_ord_id: Option<String>,
325    #[serde(rename = "receivedTime", default)]
326    pub received_time: Option<String>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
330#[serde(rename_all = "camelCase")]
331pub struct FuturesSendOrderEvent {
332    #[serde(rename = "type")]
333    pub event_type: KrakenFuturesOrderEventType,
334    #[serde(default)]
335    pub order: Option<FuturesOrderEventData>,
336    #[serde(default)]
337    pub order_trigger: Option<FuturesOrderTriggerData>,
338    #[serde(default)]
339    pub reduced_quantity: Option<f64>,
340    // Execution event fields
341    #[serde(rename = "executionId", default)]
342    pub execution_id: Option<String>,
343    #[serde(default)]
344    pub price: Option<f64>,
345    #[serde(default)]
346    pub amount: Option<f64>,
347    #[serde(rename = "orderPriorEdit", default)]
348    pub order_prior_edit: Option<Box<FuturesOrderEventData>>,
349    #[serde(rename = "orderPriorExecution", default)]
350    pub order_prior_execution: Option<Box<FuturesOrderEventData>>,
351    #[serde(rename = "takerReducedQuantity", default)]
352    pub taker_reduced_quantity: Option<f64>,
353    // Reject event fields
354    #[serde(default)]
355    pub reason: Option<String>,
356    #[serde(default)]
357    pub uid: Option<String>,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize)]
361#[serde(rename_all = "camelCase")]
362pub struct FuturesOrderEventData {
363    #[serde(rename = "orderId")]
364    pub order_id: String,
365    #[serde(rename = "cliOrdId", default)]
366    pub cli_ord_id: Option<String>,
367    #[serde(rename = "type")]
368    pub order_type: KrakenFuturesOrderType,
369    pub symbol: String,
370    pub side: KrakenOrderSide,
371    pub quantity: f64,
372    pub filled: f64,
373    #[serde(rename = "limitPrice", default)]
374    pub limit_price: Option<f64>,
375    #[serde(rename = "stopPrice", default)]
376    pub stop_price: Option<f64>,
377    pub timestamp: String,
378    #[serde(rename = "lastUpdateTimestamp")]
379    pub last_update_timestamp: String,
380    #[serde(rename = "reduceOnly", default)]
381    pub reduce_only: bool,
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
385#[serde(rename_all = "camelCase")]
386pub struct FuturesOrderTriggerData {
387    pub uid: String,
388    #[serde(rename = "clientId", default)]
389    pub client_id: Option<String>,
390    #[serde(rename = "type")]
391    pub order_type: KrakenFuturesOrderType,
392    pub symbol: String,
393    pub side: KrakenOrderSide,
394    pub quantity: f64,
395    #[serde(rename = "limitPrice", default)]
396    pub limit_price: Option<f64>,
397    #[serde(rename = "limitPriceOffsetValue", default)]
398    pub limit_price_offset_value: Option<f64>,
399    #[serde(rename = "limitPriceOffsetUnit", default)]
400    pub limit_price_offset_unit: Option<String>,
401    #[serde(rename = "triggerPrice")]
402    pub trigger_price: f64,
403    #[serde(rename = "triggerSide")]
404    pub trigger_side: KrakenTriggerSide,
405    #[serde(rename = "triggerSignal")]
406    pub trigger_signal: KrakenTriggerSignal,
407    #[serde(rename = "reduceOnly", default)]
408    pub reduce_only: bool,
409    pub timestamp: String,
410    #[serde(rename = "lastUpdateTimestamp")]
411    pub last_update_timestamp: String,
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize)]
415#[serde(rename_all = "camelCase")]
416pub struct FuturesCancelOrderResponse {
417    pub result: KrakenApiResult,
418    #[serde(default)]
419    pub server_time: Option<String>,
420    pub cancel_status: FuturesCancelStatus,
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize)]
424#[serde(rename_all = "camelCase")]
425pub struct FuturesCancelStatus {
426    pub status: KrakenSendStatus,
427    #[serde(rename = "order_id", default)]
428    pub order_id: Option<String>,
429    #[serde(rename = "cli_ord_id", default)]
430    pub cli_ord_id: Option<String>,
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
434#[serde(rename_all = "camelCase")]
435pub struct FuturesEditOrderResponse {
436    pub result: KrakenApiResult,
437    #[serde(default)]
438    pub server_time: Option<String>,
439    pub edit_status: FuturesEditStatus,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize)]
443#[serde(rename_all = "camelCase")]
444pub struct FuturesEditStatus {
445    pub status: String,
446    #[serde(rename = "order_id", default)]
447    pub order_id: Option<String>,
448    #[serde(rename = "cli_ord_id", default)]
449    pub cli_ord_id: Option<String>,
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize)]
453#[serde(rename_all = "camelCase")]
454pub struct FuturesBatchOrderResponse {
455    pub result: KrakenApiResult,
456    #[serde(default)]
457    pub server_time: Option<String>,
458    pub batch_status: Vec<FuturesSendStatus>,
459}
460
461/// Response for batch cancel operations via `/derivatives/api/v3/batchorder`.
462///
463/// When sending only cancel operations, the response has a different format
464/// with individual cancel status items.
465#[derive(Debug, Clone, Serialize, Deserialize)]
466#[serde(rename_all = "camelCase")]
467pub struct FuturesBatchCancelResponse {
468    pub result: KrakenApiResult,
469    #[serde(default)]
470    pub server_time: Option<String>,
471    #[serde(default)]
472    pub error: Option<String>,
473    #[serde(default)]
474    pub batch_status: Vec<FuturesBatchCancelStatus>,
475}
476
477#[derive(Debug, Clone, Serialize, Deserialize)]
478#[serde(rename_all = "camelCase")]
479pub struct FuturesBatchCancelStatus {
480    #[serde(default)]
481    pub order_id: Option<String>,
482    #[serde(default)]
483    pub cli_ord_id: Option<String>,
484    #[serde(default)]
485    pub status: Option<KrakenSendStatus>,
486    #[serde(default)]
487    pub cancel_status: Option<FuturesCancelStatus>,
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize)]
491#[serde(rename_all = "camelCase")]
492pub struct FuturesCancelAllOrdersResponse {
493    pub result: KrakenApiResult,
494    #[serde(default)]
495    pub server_time: Option<String>,
496    pub cancel_status: FuturesCancelAllStatus,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
500#[serde(rename_all = "camelCase")]
501pub struct FuturesCancelAllStatus {
502    pub status: KrakenSendStatus,
503    #[serde(default)]
504    pub cancelled_orders: Vec<CancelledOrder>,
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
508#[serde(rename_all = "camelCase")]
509pub struct CancelledOrder {
510    #[serde(rename = "order_id", default)]
511    pub order_id: Option<String>,
512}
513
514// Futures Public Executions Models
515
516/// Response from the Kraken Futures public executions endpoint.
517#[derive(Debug, Clone, Serialize, Deserialize)]
518#[serde(rename_all = "camelCase")]
519pub struct FuturesPublicExecutionsResponse {
520    pub elements: Vec<FuturesPublicExecutionElement>,
521    #[serde(default)]
522    pub len: Option<i64>,
523    #[serde(default)]
524    pub continuation_token: Option<String>,
525}
526
527/// A single execution element from the public executions response.
528#[derive(Debug, Clone, Serialize, Deserialize)]
529pub struct FuturesPublicExecutionElement {
530    pub uid: String,
531    pub timestamp: i64,
532    pub event: FuturesPublicExecutionEvent,
533}
534
535/// The event wrapper containing the execution details.
536#[derive(Debug, Clone, Serialize, Deserialize)]
537pub struct FuturesPublicExecutionEvent {
538    #[serde(rename = "Execution")]
539    pub execution: FuturesPublicExecutionWrapper,
540}
541
542/// Wrapper containing the actual execution data.
543#[derive(Debug, Clone, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct FuturesPublicExecutionWrapper {
546    pub execution: FuturesPublicExecution,
547    #[serde(default)]
548    pub taker_reduced_quantity: Option<String>,
549}
550
551/// The actual execution/trade data.
552#[derive(Debug, Clone, Serialize, Deserialize)]
553#[serde(rename_all = "camelCase")]
554pub struct FuturesPublicExecution {
555    pub uid: String,
556    pub maker_order: FuturesPublicOrder,
557    pub taker_order: FuturesPublicOrder,
558    pub timestamp: i64,
559    pub quantity: String,
560    pub price: String,
561    #[serde(default)]
562    pub mark_price: Option<String>,
563    #[serde(default)]
564    pub limit_filled: Option<bool>,
565    #[serde(default)]
566    pub usd_value: Option<String>,
567}
568
569/// Order information within an execution.
570#[derive(Debug, Clone, Serialize, Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub struct FuturesPublicOrder {
573    pub uid: String,
574    pub tradeable: String,
575    pub direction: String,
576    pub quantity: String,
577    pub timestamp: i64,
578    #[serde(default)]
579    pub limit_price: Option<String>,
580    #[serde(default)]
581    pub order_type: Option<String>,
582    #[serde(default)]
583    pub reduce_only: Option<bool>,
584    #[serde(default)]
585    pub last_update_timestamp: Option<i64>,
586}
587
588// Futures Accounts Models
589
590/// Response from the Kraken Futures accounts endpoint.
591#[derive(Debug, Clone, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct FuturesAccountsResponse {
594    pub result: KrakenApiResult,
595    #[serde(default)]
596    pub accounts: std::collections::HashMap<String, FuturesAccount>,
597    #[serde(default)]
598    pub error: Option<String>,
599    #[serde(default)]
600    pub server_time: Option<String>,
601}
602
603/// A Kraken Futures account (margin or multi-collateral).
604#[derive(Debug, Clone, Serialize, Deserialize)]
605#[serde(rename_all = "camelCase")]
606pub struct FuturesAccount {
607    #[serde(rename = "type")]
608    pub account_type: String,
609    /// Balances for margin accounts (symbol -> amount).
610    #[serde(default)]
611    pub balances: std::collections::HashMap<String, f64>,
612    /// Currencies for flex/multi-collateral accounts.
613    #[serde(default)]
614    pub currencies: std::collections::HashMap<String, FuturesFlexCurrency>,
615    /// Auxiliary info for margin accounts.
616    #[serde(default)]
617    pub auxiliary: Option<FuturesAuxiliary>,
618    /// Margin requirements.
619    #[serde(default)]
620    pub margin_requirements: Option<FuturesMarginRequirements>,
621    /// Portfolio value (for flex accounts).
622    #[serde(default)]
623    pub portfolio_value: Option<f64>,
624    /// Available margin (for flex accounts).
625    #[serde(default)]
626    pub available_margin: Option<f64>,
627    /// Initial margin (for flex accounts).
628    #[serde(default)]
629    pub initial_margin: Option<f64>,
630    /// PnL (for flex accounts).
631    #[serde(default)]
632    pub pnl: Option<f64>,
633}
634
635/// Currency info for flex/multi-collateral accounts.
636#[derive(Debug, Clone, Serialize, Deserialize)]
637#[serde(rename_all = "camelCase")]
638pub struct FuturesFlexCurrency {
639    pub quantity: f64,
640    #[serde(default)]
641    pub value: Option<f64>,
642    #[serde(default)]
643    pub collateral: Option<f64>,
644    #[serde(default)]
645    pub available: Option<f64>,
646}
647
648/// Auxiliary account info for margin accounts.
649#[derive(Debug, Clone, Serialize, Deserialize)]
650#[serde(rename_all = "camelCase")]
651pub struct FuturesAuxiliary {
652    #[serde(default)]
653    pub usd: Option<f64>,
654    /// Portfolio value.
655    #[serde(default)]
656    pub pv: Option<f64>,
657    /// Profit/loss.
658    #[serde(default)]
659    pub pnl: Option<f64>,
660    /// Available funds.
661    #[serde(default)]
662    pub af: Option<f64>,
663    #[serde(default)]
664    pub funding: Option<f64>,
665}
666
667/// Margin requirements for an account.
668#[derive(Debug, Clone, Serialize, Deserialize)]
669#[serde(rename_all = "camelCase")]
670pub struct FuturesMarginRequirements {
671    /// Initial margin.
672    #[serde(default)]
673    pub im: Option<f64>,
674    /// Maintenance margin.
675    #[serde(default)]
676    pub mm: Option<f64>,
677    /// Liquidation threshold.
678    #[serde(default)]
679    pub lt: Option<f64>,
680    /// Termination threshold.
681    #[serde(default)]
682    pub tt: Option<f64>,
683}
684
685#[cfg(test)]
686mod tests {
687    use rstest::rstest;
688
689    use super::*;
690
691    fn load_test_data(filename: &str) -> String {
692        let path = format!("test_data/{filename}");
693        std::fs::read_to_string(&path)
694            .unwrap_or_else(|e| panic!("Failed to load test data from {path}: {e}"))
695    }
696
697    #[rstest]
698    fn test_parse_futures_open_orders() {
699        let data = load_test_data("http_futures_open_orders.json");
700        let response: FuturesOpenOrdersResponse =
701            serde_json::from_str(&data).expect("Failed to parse futures open orders");
702
703        assert_eq!(response.result, KrakenApiResult::Success);
704        assert_eq!(response.open_orders.len(), 3);
705
706        let order = &response.open_orders[0];
707        assert_eq!(order.order_id, "2ce038ae-c144-4de7-a0f1-82f7f4fca864");
708        assert_eq!(order.symbol, "PI_ETHUSD");
709        assert_eq!(order.side, KrakenOrderSide::Buy);
710        assert_eq!(order.order_type, KrakenFuturesOrderType::Limit);
711        assert_eq!(order.limit_price, Some(1200.0));
712        assert_eq!(order.unfilled_size, 100.0);
713        assert_eq!(order.filled_size, 0.0);
714    }
715
716    #[rstest]
717    fn test_parse_futures_fills() {
718        let data = load_test_data("http_futures_fills.json");
719        let response: FuturesFillsResponse =
720            serde_json::from_str(&data).expect("Failed to parse futures fills");
721
722        assert_eq!(response.result, KrakenApiResult::Success);
723        assert_eq!(response.fills.len(), 3);
724
725        let fill = &response.fills[0];
726        assert_eq!(fill.fill_id, "cad76f07-814e-4dc6-8478-7867407b6bff");
727        assert_eq!(fill.symbol, "PI_XBTUSD");
728        assert_eq!(fill.side, KrakenOrderSide::Buy);
729        assert_eq!(fill.size, 5000.0);
730        assert_eq!(fill.price, 27937.5);
731        assert_eq!(fill.fill_type, KrakenFillType::Maker);
732        assert_eq!(fill.fee_currency, Some("BTC".to_string()));
733    }
734
735    #[rstest]
736    fn test_parse_futures_open_positions() {
737        let data = load_test_data("http_futures_open_positions.json");
738        let response: FuturesOpenPositionsResponse =
739            serde_json::from_str(&data).expect("Failed to parse futures open positions");
740
741        assert_eq!(response.result, KrakenApiResult::Success);
742        assert_eq!(response.open_positions.len(), 2);
743
744        let position = &response.open_positions[0];
745        assert_eq!(position.side, KrakenPositionSide::Short);
746        assert_eq!(position.symbol, "PI_XBTUSD");
747        assert_eq!(position.size, 8000.0);
748        assert!(position.unrealized_funding.is_some());
749    }
750}