deribit_base/model/
request.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 21/7/25
5******************************************************************************/
6use crate::model::order::{OrderSide, OrderType, TimeInForce};
7use pretty_simple_display::{DebugPretty, DisplaySimple};
8
9use serde::{Deserialize, Serialize};
10
11/// FIX protocol compatible structures
12pub mod fix {
13    use super::*;
14
15    /// New order request structure for FIX protocol
16    #[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
17    pub struct NewOrderRequest {
18        /// Instrument symbol (e.g., "BTC-PERPETUAL")
19        pub symbol: String,
20        /// Order side (buy/sell)
21        pub side: OrderSide,
22        /// Order type
23        pub order_type: OrderType,
24        /// Order quantity
25        pub quantity: f64,
26        /// Order price (required for limit orders)
27        pub price: Option<f64>,
28        /// Time in force
29        pub time_in_force: TimeInForce,
30        /// Client order ID
31        pub client_order_id: Option<String>,
32    }
33
34    impl NewOrderRequest {
35        /// Create a new market buy order
36        pub fn market_buy(symbol: String, quantity: f64) -> Self {
37            Self {
38                symbol,
39                side: OrderSide::Buy,
40                order_type: OrderType::Market,
41                quantity,
42                price: None,
43                time_in_force: TimeInForce::ImmediateOrCancel,
44                client_order_id: None,
45            }
46        }
47
48        /// Create a new market sell order
49        pub fn market_sell(symbol: String, quantity: f64) -> Self {
50            Self {
51                symbol,
52                side: OrderSide::Sell,
53                order_type: OrderType::Market,
54                quantity,
55                price: None,
56                time_in_force: TimeInForce::ImmediateOrCancel,
57                client_order_id: None,
58            }
59        }
60
61        /// Create a new limit buy order
62        pub fn limit_buy(symbol: String, quantity: f64, price: f64) -> Self {
63            Self {
64                symbol,
65                side: OrderSide::Buy,
66                order_type: OrderType::Limit,
67                quantity,
68                price: Some(price),
69                time_in_force: TimeInForce::GoodTilCancelled,
70                client_order_id: None,
71            }
72        }
73
74        /// Create a new limit sell order
75        pub fn limit_sell(symbol: String, quantity: f64, price: f64) -> Self {
76            Self {
77                symbol,
78                side: OrderSide::Sell,
79                order_type: OrderType::Limit,
80                quantity,
81                price: Some(price),
82                time_in_force: TimeInForce::GoodTilCancelled,
83                client_order_id: None,
84            }
85        }
86
87        /// Set client order ID
88        pub fn with_client_order_id(mut self, client_order_id: String) -> Self {
89            self.client_order_id = Some(client_order_id);
90            self
91        }
92
93        /// Set time in force
94        pub fn with_time_in_force(mut self, tif: TimeInForce) -> Self {
95            self.time_in_force = tif;
96            self
97        }
98    }
99
100    /// Convert from REST/WebSocket NewOrderRequest to FIX NewOrderRequest
101    impl From<super::NewOrderRequest> for NewOrderRequest {
102        fn from(rest_order: super::NewOrderRequest) -> Self {
103            Self {
104                symbol: rest_order.instrument_name,
105                side: rest_order.side,
106                order_type: rest_order.order_type,
107                quantity: rest_order.amount,
108                price: rest_order.price,
109                time_in_force: rest_order.time_in_force,
110                client_order_id: rest_order.client_order_id,
111            }
112        }
113    }
114
115    /// Convert from FIX NewOrderRequest to REST/WebSocket NewOrderRequest
116    impl From<NewOrderRequest> for super::NewOrderRequest {
117        fn from(fix_order: NewOrderRequest) -> Self {
118            Self {
119                instrument_name: fix_order.symbol,
120                amount: fix_order.quantity,
121                order_type: fix_order.order_type,
122                side: fix_order.side,
123                price: fix_order.price,
124                time_in_force: fix_order.time_in_force,
125                post_only: None,
126                reduce_only: None,
127                label: None,
128                stop_price: None,
129                trigger: None,
130                advanced: None,
131                max_show: None,
132                reject_post_only: None,
133                valid_until: None,
134                client_order_id: fix_order.client_order_id,
135            }
136        }
137    }
138}
139
140/// Generic request for creating new orders
141#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
142pub struct NewOrderRequest {
143    /// Instrument name
144    pub instrument_name: String,
145    /// Order amount
146    pub amount: f64,
147    /// Order type
148    #[serde(rename = "type")]
149    pub order_type: OrderType,
150    /// Order side (buy/sell)
151    pub side: OrderSide,
152    /// Order price (required for limit orders)
153    pub price: Option<f64>,
154    /// Time in force
155    pub time_in_force: TimeInForce,
156    /// Post-only flag
157    pub post_only: Option<bool>,
158    /// Reduce-only flag
159    pub reduce_only: Option<bool>,
160    /// Order label
161    pub label: Option<String>,
162    /// Stop price for stop orders
163    pub stop_price: Option<f64>,
164    /// Trigger type for stop orders
165    pub trigger: Option<TriggerType>,
166    /// Advanced order type
167    pub advanced: Option<AdvancedOrderType>,
168    /// Maximum show amount (iceberg orders)
169    pub max_show: Option<f64>,
170    /// Reject post-only flag
171    pub reject_post_only: Option<bool>,
172    /// Valid until timestamp
173    pub valid_until: Option<i64>,
174    /// Client order ID for tracking
175    pub client_order_id: Option<String>,
176}
177
178impl NewOrderRequest {
179    /// Create a new market buy order
180    pub fn market_buy(instrument_name: String, amount: f64) -> Self {
181        Self {
182            instrument_name,
183            amount,
184            order_type: OrderType::Market,
185            side: OrderSide::Buy,
186            price: None,
187            time_in_force: TimeInForce::ImmediateOrCancel,
188            post_only: None,
189            reduce_only: None,
190            label: None,
191            stop_price: None,
192            trigger: None,
193            advanced: None,
194            max_show: None,
195            reject_post_only: None,
196            valid_until: None,
197            client_order_id: None,
198        }
199    }
200
201    /// Create a new market sell order
202    pub fn market_sell(instrument_name: String, amount: f64) -> Self {
203        Self {
204            instrument_name,
205            amount,
206            order_type: OrderType::Market,
207            side: OrderSide::Sell,
208            price: None,
209            time_in_force: TimeInForce::ImmediateOrCancel,
210            post_only: None,
211            reduce_only: None,
212            label: None,
213            stop_price: None,
214            trigger: None,
215            advanced: None,
216            max_show: None,
217            reject_post_only: None,
218            valid_until: None,
219            client_order_id: None,
220        }
221    }
222
223    /// Create a new limit buy order
224    pub fn limit_buy(instrument_name: String, amount: f64, price: f64) -> Self {
225        Self {
226            instrument_name,
227            amount,
228            order_type: OrderType::Limit,
229            side: OrderSide::Buy,
230            price: Some(price),
231            time_in_force: TimeInForce::GoodTilCancelled,
232            post_only: None,
233            reduce_only: None,
234            label: None,
235            stop_price: None,
236            trigger: None,
237            advanced: None,
238            max_show: None,
239            reject_post_only: None,
240            valid_until: None,
241            client_order_id: None,
242        }
243    }
244
245    /// Create a new limit sell order
246    pub fn limit_sell(instrument_name: String, amount: f64, price: f64) -> Self {
247        Self {
248            instrument_name,
249            amount,
250            order_type: OrderType::Limit,
251            side: OrderSide::Sell,
252            price: Some(price),
253            time_in_force: TimeInForce::GoodTilCancelled,
254            post_only: None,
255            reduce_only: None,
256            label: None,
257            stop_price: None,
258            trigger: None,
259            advanced: None,
260            max_show: None,
261            reject_post_only: None,
262            valid_until: None,
263            client_order_id: None,
264        }
265    }
266
267    /// Set the order as post-only
268    pub fn with_post_only(mut self, post_only: bool) -> Self {
269        self.post_only = Some(post_only);
270        self
271    }
272
273    /// Set the order as reduce-only
274    pub fn with_reduce_only(mut self, reduce_only: bool) -> Self {
275        self.reduce_only = Some(reduce_only);
276        self
277    }
278
279    /// Set order label
280    pub fn with_label(mut self, label: String) -> Self {
281        self.label = Some(label);
282        self
283    }
284
285    /// Set time in force
286    pub fn with_time_in_force(mut self, tif: TimeInForce) -> Self {
287        self.time_in_force = tif;
288        self
289    }
290}
291
292/// Trigger type for stop orders
293#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Eq, Serialize, Deserialize)]
294#[serde(rename_all = "snake_case")]
295pub enum TriggerType {
296    /// Index price trigger
297    IndexPrice,
298    /// Mark price trigger
299    MarkPrice,
300    /// Last price trigger
301    LastPrice,
302}
303
304/// Advanced order type
305#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
306#[serde(rename_all = "lowercase")]
307pub enum AdvancedOrderType {
308    /// USD denomination
309    Usd,
310    /// Implied volatility
311    Implv,
312}
313
314/// Order modification request
315#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
316pub struct ModifyOrderRequest {
317    /// Order ID to modify
318    pub order_id: String,
319    /// New amount
320    pub amount: Option<f64>,
321    /// New price
322    pub price: Option<f64>,
323    /// New stop price
324    pub stop_price: Option<f64>,
325    /// New post-only flag
326    pub post_only: Option<bool>,
327    /// New reduce-only flag
328    pub reduce_only: Option<bool>,
329    /// New reject post-only flag
330    pub reject_post_only: Option<bool>,
331    /// New advanced order type
332    pub advanced: Option<AdvancedOrderType>,
333}
334
335/// Order cancellation request
336#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
337pub struct CancelOrderRequest {
338    /// Order ID to cancel
339    pub order_id: String,
340}
341
342/// Cancel all orders request
343#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
344pub struct CancelAllOrdersRequest {
345    /// Currency filter
346    pub currency: Option<String>,
347    /// Instrument kind filter
348    pub kind: Option<String>,
349    /// Instrument type filter
350    #[serde(rename = "type")]
351    pub instrument_type: Option<String>,
352}
353
354/// Position close request
355#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
356pub struct ClosePositionRequest {
357    /// Instrument name
358    pub instrument_name: String,
359    /// Order type for closing
360    #[serde(rename = "type")]
361    pub order_type: OrderType,
362    /// Price for limit orders
363    pub price: Option<f64>,
364}
365
366/// Authentication request
367#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
368pub struct AuthRequest {
369    /// Grant type
370    pub grant_type: String,
371    /// Client ID
372    pub client_id: String,
373    /// Client secret
374    pub client_secret: String,
375    /// Refresh token (for refresh grant)
376    pub refresh_token: Option<String>,
377    /// Scope
378    pub scope: Option<String>,
379}
380
381impl AuthRequest {
382    /// Create a client credentials authentication request
383    pub fn client_credentials(client_id: String, client_secret: String) -> Self {
384        Self {
385            grant_type: "client_credentials".to_string(),
386            client_id,
387            client_secret,
388            refresh_token: None,
389            scope: None,
390        }
391    }
392
393    /// Create a refresh token authentication request
394    pub fn refresh_token(client_id: String, client_secret: String, refresh_token: String) -> Self {
395        Self {
396            grant_type: "refresh_token".to_string(),
397            client_id,
398            client_secret,
399            refresh_token: Some(refresh_token),
400            scope: None,
401        }
402    }
403}
404
405#[cfg(test)]
406mod tests {
407    use super::*;
408
409    #[test]
410    fn test_fix_new_order_request_market_buy() {
411        let order = fix::NewOrderRequest::market_buy("BTC-PERPETUAL".to_string(), 1.0);
412        assert_eq!(order.symbol, "BTC-PERPETUAL");
413        assert_eq!(order.side, OrderSide::Buy);
414        assert_eq!(order.order_type, OrderType::Market);
415        assert_eq!(order.quantity, 1.0);
416        assert_eq!(order.price, None);
417        assert_eq!(order.time_in_force, TimeInForce::ImmediateOrCancel);
418        assert_eq!(order.client_order_id, None);
419    }
420
421    #[test]
422    fn test_fix_new_order_request_market_sell() {
423        let order = fix::NewOrderRequest::market_sell("ETH-PERPETUAL".to_string(), 2.0);
424        assert_eq!(order.symbol, "ETH-PERPETUAL");
425        assert_eq!(order.side, OrderSide::Sell);
426        assert_eq!(order.order_type, OrderType::Market);
427        assert_eq!(order.quantity, 2.0);
428        assert_eq!(order.price, None);
429        assert_eq!(order.time_in_force, TimeInForce::ImmediateOrCancel);
430    }
431
432    #[test]
433    fn test_fix_new_order_request_limit_buy() {
434        let order = fix::NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0);
435        assert_eq!(order.symbol, "BTC-PERPETUAL");
436        assert_eq!(order.side, OrderSide::Buy);
437        assert_eq!(order.order_type, OrderType::Limit);
438        assert_eq!(order.quantity, 1.0);
439        assert_eq!(order.price, Some(50000.0));
440        assert_eq!(order.time_in_force, TimeInForce::GoodTilCancelled);
441    }
442
443    #[test]
444    fn test_fix_new_order_request_limit_sell() {
445        let order = fix::NewOrderRequest::limit_sell("ETH-PERPETUAL".to_string(), 2.0, 3500.0);
446        assert_eq!(order.symbol, "ETH-PERPETUAL");
447        assert_eq!(order.side, OrderSide::Sell);
448        assert_eq!(order.order_type, OrderType::Limit);
449        assert_eq!(order.quantity, 2.0);
450        assert_eq!(order.price, Some(3500.0));
451        assert_eq!(order.time_in_force, TimeInForce::GoodTilCancelled);
452    }
453
454    #[test]
455    fn test_fix_new_order_request_with_client_order_id() {
456        let order = fix::NewOrderRequest::market_buy("BTC-PERPETUAL".to_string(), 1.0)
457            .with_client_order_id("CLIENT_ORDER_123".to_string());
458        assert_eq!(order.client_order_id, Some("CLIENT_ORDER_123".to_string()));
459    }
460
461    #[test]
462    fn test_fix_new_order_request_with_time_in_force() {
463        let order = fix::NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
464            .with_time_in_force(TimeInForce::FillOrKill);
465        assert_eq!(order.time_in_force, TimeInForce::FillOrKill);
466    }
467
468    #[test]
469    fn test_new_order_request_market_buy() {
470        let order = NewOrderRequest::market_buy("BTC-PERPETUAL".to_string(), 1.0);
471        assert_eq!(order.instrument_name, "BTC-PERPETUAL");
472        assert_eq!(order.amount, 1.0);
473        assert_eq!(order.order_type, OrderType::Market);
474        assert_eq!(order.side, OrderSide::Buy);
475        assert_eq!(order.price, None);
476        assert_eq!(order.time_in_force, TimeInForce::ImmediateOrCancel);
477        assert_eq!(order.post_only, None);
478        assert_eq!(order.reduce_only, None);
479    }
480
481    #[test]
482    fn test_new_order_request_market_sell() {
483        let order = NewOrderRequest::market_sell("ETH-PERPETUAL".to_string(), 2.0);
484        assert_eq!(order.instrument_name, "ETH-PERPETUAL");
485        assert_eq!(order.amount, 2.0);
486        assert_eq!(order.order_type, OrderType::Market);
487        assert_eq!(order.side, OrderSide::Sell);
488        assert_eq!(order.price, None);
489        assert_eq!(order.time_in_force, TimeInForce::ImmediateOrCancel);
490    }
491
492    #[test]
493    fn test_new_order_request_limit_buy() {
494        let order = NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0);
495        assert_eq!(order.instrument_name, "BTC-PERPETUAL");
496        assert_eq!(order.amount, 1.0);
497        assert_eq!(order.order_type, OrderType::Limit);
498        assert_eq!(order.side, OrderSide::Buy);
499        assert_eq!(order.price, Some(50000.0));
500        assert_eq!(order.time_in_force, TimeInForce::GoodTilCancelled);
501    }
502
503    #[test]
504    fn test_new_order_request_limit_sell() {
505        let order = NewOrderRequest::limit_sell("ETH-PERPETUAL".to_string(), 2.0, 3500.0);
506        assert_eq!(order.instrument_name, "ETH-PERPETUAL");
507        assert_eq!(order.amount, 2.0);
508        assert_eq!(order.order_type, OrderType::Limit);
509        assert_eq!(order.side, OrderSide::Sell);
510        assert_eq!(order.price, Some(3500.0));
511        assert_eq!(order.time_in_force, TimeInForce::GoodTilCancelled);
512    }
513
514    #[test]
515    fn test_new_order_request_with_post_only() {
516        let order = NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
517            .with_post_only(true);
518        assert_eq!(order.post_only, Some(true));
519    }
520
521    #[test]
522    fn test_new_order_request_with_reduce_only() {
523        let order = NewOrderRequest::limit_sell("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
524            .with_reduce_only(true);
525        assert_eq!(order.reduce_only, Some(true));
526    }
527
528    #[test]
529    fn test_new_order_request_with_label() {
530        let order = NewOrderRequest::market_buy("BTC-PERPETUAL".to_string(), 1.0)
531            .with_label("test_order".to_string());
532        assert_eq!(order.label, Some("test_order".to_string()));
533    }
534
535    #[test]
536    fn test_new_order_request_with_time_in_force() {
537        let order = NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
538            .with_time_in_force(TimeInForce::FillOrKill);
539        assert_eq!(order.time_in_force, TimeInForce::FillOrKill);
540    }
541
542    #[test]
543    fn test_trigger_type_serialization() {
544        let trigger_types = vec![
545            TriggerType::IndexPrice,
546            TriggerType::MarkPrice,
547            TriggerType::LastPrice,
548        ];
549
550        for trigger_type in trigger_types {
551            let json = serde_json::to_string(&trigger_type).unwrap();
552            let deserialized: TriggerType = serde_json::from_str(&json).unwrap();
553            assert_eq!(trigger_type, deserialized);
554        }
555    }
556
557    #[test]
558    fn test_advanced_order_type_serialization() {
559        let advanced_types = vec![AdvancedOrderType::Usd, AdvancedOrderType::Implv];
560
561        for advanced_type in advanced_types {
562            let json = serde_json::to_string(&advanced_type).unwrap();
563            let deserialized: AdvancedOrderType = serde_json::from_str(&json).unwrap();
564            assert_eq!(advanced_type, deserialized);
565        }
566    }
567
568    #[test]
569    fn test_modify_order_request() {
570        let modify_request = ModifyOrderRequest {
571            order_id: "ORDER_123".to_string(),
572            amount: Some(2.0),
573            price: Some(51000.0),
574            stop_price: None,
575            post_only: Some(true),
576            reduce_only: Some(false),
577            reject_post_only: None,
578            advanced: Some(AdvancedOrderType::Usd),
579        };
580
581        assert_eq!(modify_request.order_id, "ORDER_123");
582        assert_eq!(modify_request.amount, Some(2.0));
583        assert_eq!(modify_request.price, Some(51000.0));
584        assert_eq!(modify_request.post_only, Some(true));
585        assert_eq!(modify_request.advanced, Some(AdvancedOrderType::Usd));
586    }
587
588    #[test]
589    fn test_cancel_order_request() {
590        let cancel_request = CancelOrderRequest {
591            order_id: "ORDER_123".to_string(),
592        };
593        assert_eq!(cancel_request.order_id, "ORDER_123");
594    }
595
596    #[test]
597    fn test_cancel_all_orders_request() {
598        let cancel_all_request = CancelAllOrdersRequest {
599            currency: Some("BTC".to_string()),
600            kind: Some("future".to_string()),
601            instrument_type: Some("perpetual".to_string()),
602        };
603
604        assert_eq!(cancel_all_request.currency, Some("BTC".to_string()));
605        assert_eq!(cancel_all_request.kind, Some("future".to_string()));
606        assert_eq!(
607            cancel_all_request.instrument_type,
608            Some("perpetual".to_string())
609        );
610    }
611
612    #[test]
613    fn test_cancel_all_orders_request_empty() {
614        let cancel_all_request = CancelAllOrdersRequest {
615            currency: None,
616            kind: None,
617            instrument_type: None,
618        };
619
620        assert_eq!(cancel_all_request.currency, None);
621        assert_eq!(cancel_all_request.kind, None);
622        assert_eq!(cancel_all_request.instrument_type, None);
623    }
624
625    #[test]
626    fn test_close_position_request() {
627        let close_request = ClosePositionRequest {
628            instrument_name: "BTC-PERPETUAL".to_string(),
629            order_type: OrderType::Market,
630            price: None,
631        };
632
633        assert_eq!(close_request.instrument_name, "BTC-PERPETUAL");
634        assert_eq!(close_request.order_type, OrderType::Market);
635        assert_eq!(close_request.price, None);
636
637        let close_limit_request = ClosePositionRequest {
638            instrument_name: "ETH-PERPETUAL".to_string(),
639            order_type: OrderType::Limit,
640            price: Some(3500.0),
641        };
642
643        assert_eq!(close_limit_request.price, Some(3500.0));
644    }
645
646    #[test]
647    fn test_auth_request_client_credentials() {
648        let auth_request = AuthRequest::client_credentials(
649            "client_id_123".to_string(),
650            "client_secret_456".to_string(),
651        );
652
653        assert_eq!(auth_request.grant_type, "client_credentials");
654        assert_eq!(auth_request.client_id, "client_id_123");
655        assert_eq!(auth_request.client_secret, "client_secret_456");
656        assert_eq!(auth_request.refresh_token, None);
657        assert_eq!(auth_request.scope, None);
658    }
659
660    #[test]
661    fn test_auth_request_refresh_token() {
662        let auth_request = AuthRequest::refresh_token(
663            "client_id_123".to_string(),
664            "client_secret_456".to_string(),
665            "refresh_token_789".to_string(),
666        );
667
668        assert_eq!(auth_request.grant_type, "refresh_token");
669        assert_eq!(auth_request.client_id, "client_id_123");
670        assert_eq!(auth_request.client_secret, "client_secret_456");
671        assert_eq!(
672            auth_request.refresh_token,
673            Some("refresh_token_789".to_string())
674        );
675        assert_eq!(auth_request.scope, None);
676    }
677
678    #[test]
679    fn test_fix_to_rest_conversion() {
680        let fix_order = fix::NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
681            .with_client_order_id("CLIENT_ORDER_123".to_string());
682
683        let rest_order: NewOrderRequest = fix_order.into();
684
685        assert_eq!(rest_order.instrument_name, "BTC-PERPETUAL");
686        assert_eq!(rest_order.amount, 1.0);
687        assert_eq!(rest_order.order_type, OrderType::Limit);
688        assert_eq!(rest_order.side, OrderSide::Buy);
689        assert_eq!(rest_order.price, Some(50000.0));
690        assert_eq!(
691            rest_order.client_order_id,
692            Some("CLIENT_ORDER_123".to_string())
693        );
694        assert_eq!(rest_order.post_only, None);
695        assert_eq!(rest_order.reduce_only, None);
696    }
697
698    #[test]
699    fn test_rest_to_fix_conversion() {
700        let rest_order = NewOrderRequest::limit_sell("ETH-PERPETUAL".to_string(), 2.0, 3500.0)
701            .with_label("test_order".to_string())
702            .with_post_only(true);
703
704        let fix_order: fix::NewOrderRequest = rest_order.into();
705
706        assert_eq!(fix_order.symbol, "ETH-PERPETUAL");
707        assert_eq!(fix_order.quantity, 2.0);
708        assert_eq!(fix_order.order_type, OrderType::Limit);
709        assert_eq!(fix_order.side, OrderSide::Sell);
710        assert_eq!(fix_order.price, Some(3500.0));
711        assert_eq!(fix_order.time_in_force, TimeInForce::GoodTilCancelled);
712    }
713
714    #[test]
715    fn test_serialization_roundtrip() {
716        let order = NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
717            .with_post_only(true)
718            .with_label("test_order".to_string());
719
720        let json = serde_json::to_string(&order).unwrap();
721        let deserialized: NewOrderRequest = serde_json::from_str(&json).unwrap();
722
723        assert_eq!(order.instrument_name, deserialized.instrument_name);
724        assert_eq!(order.amount, deserialized.amount);
725        assert_eq!(order.order_type, deserialized.order_type);
726        assert_eq!(order.side, deserialized.side);
727        assert_eq!(order.price, deserialized.price);
728        assert_eq!(order.post_only, deserialized.post_only);
729        assert_eq!(order.label, deserialized.label);
730    }
731
732    #[test]
733    fn test_debug_and_display_implementations() {
734        let order = NewOrderRequest::market_buy("BTC-PERPETUAL".to_string(), 1.0);
735        let debug_str = format!("{:?}", order);
736        let display_str = format!("{}", order);
737
738        assert!(debug_str.contains("BTC-PERPETUAL"));
739        assert!(display_str.contains("BTC-PERPETUAL"));
740
741        let auth_request =
742            AuthRequest::client_credentials("client_id".to_string(), "client_secret".to_string());
743        let auth_debug = format!("{:?}", auth_request);
744        let auth_display = format!("{}", auth_request);
745
746        assert!(auth_debug.contains("client_credentials"));
747        assert!(auth_display.contains("client_credentials"));
748    }
749
750    #[test]
751    fn test_enum_equality_and_cloning() {
752        let trigger1 = TriggerType::IndexPrice;
753        let trigger2 = trigger1.clone();
754        assert_eq!(trigger1, trigger2);
755
756        let advanced1 = AdvancedOrderType::Usd;
757        let advanced2 = advanced1.clone();
758        assert_eq!(advanced1, advanced2);
759    }
760
761    #[test]
762    fn test_complex_order_with_all_fields() {
763        let order = NewOrderRequest {
764            instrument_name: "BTC-PERPETUAL".to_string(),
765            amount: 1.5,
766            order_type: OrderType::StopLimit,
767            side: OrderSide::Buy,
768            price: Some(50000.0),
769            time_in_force: TimeInForce::GoodTilDay,
770            post_only: Some(true),
771            reduce_only: Some(false),
772            label: Some("complex_order".to_string()),
773            stop_price: Some(49000.0),
774            trigger: Some(TriggerType::MarkPrice),
775            advanced: Some(AdvancedOrderType::Implv),
776            max_show: Some(0.5),
777            reject_post_only: Some(false),
778            valid_until: Some(1640995200000),
779            client_order_id: Some("CLIENT_ORDER_COMPLEX".to_string()),
780        };
781
782        assert_eq!(order.instrument_name, "BTC-PERPETUAL");
783        assert_eq!(order.amount, 1.5);
784        assert_eq!(order.order_type, OrderType::StopLimit);
785        assert_eq!(order.stop_price, Some(49000.0));
786        assert_eq!(order.trigger, Some(TriggerType::MarkPrice));
787        assert_eq!(order.advanced, Some(AdvancedOrderType::Implv));
788        assert_eq!(order.max_show, Some(0.5));
789        assert_eq!(order.valid_until, Some(1640995200000));
790    }
791}