Skip to main content

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/// Linked order type for conditional order execution
305#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Eq, Serialize, Deserialize)]
306#[serde(rename_all = "snake_case")]
307pub enum LinkedOrderType {
308    /// One order triggers another (OTO)
309    OneTriggersOther,
310    /// One order cancels another (OCO)
311    OneCancelsOther,
312    /// One order triggers one and cancels another (OTOCO)
313    OneTriggersOneCancelsOther,
314}
315
316/// Trigger fill condition for linked orders
317#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Eq, Serialize, Deserialize)]
318#[serde(rename_all = "snake_case")]
319pub enum TriggerFillCondition {
320    /// Trigger on first partial fill
321    FirstHit,
322    /// Trigger only when completely filled
323    CompleteFill,
324    /// Trigger incrementally as fills occur
325    Incremental,
326}
327
328/// Advanced order type
329#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
330#[serde(rename_all = "lowercase")]
331pub enum AdvancedOrderType {
332    /// USD denomination
333    Usd,
334    /// Implied volatility
335    Implv,
336}
337
338/// Order modification request
339#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
340pub struct ModifyOrderRequest {
341    /// Order ID to modify
342    pub order_id: String,
343    /// New amount
344    pub amount: Option<f64>,
345    /// New price
346    pub price: Option<f64>,
347    /// New stop price
348    pub stop_price: Option<f64>,
349    /// New post-only flag
350    pub post_only: Option<bool>,
351    /// New reduce-only flag
352    pub reduce_only: Option<bool>,
353    /// New reject post-only flag
354    pub reject_post_only: Option<bool>,
355    /// New advanced order type
356    pub advanced: Option<AdvancedOrderType>,
357}
358
359/// Order cancellation request
360#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
361pub struct CancelOrderRequest {
362    /// Order ID to cancel
363    pub order_id: String,
364}
365
366/// Cancel all orders request
367#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
368pub struct CancelAllOrdersRequest {
369    /// Currency filter
370    pub currency: Option<String>,
371    /// Instrument kind filter
372    pub kind: Option<String>,
373    /// Instrument type filter
374    #[serde(rename = "type")]
375    pub instrument_type: Option<String>,
376}
377
378/// Position close request
379#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
380pub struct ClosePositionRequest {
381    /// Instrument name
382    pub instrument_name: String,
383    /// Order type for closing
384    #[serde(rename = "type")]
385    pub order_type: OrderType,
386    /// Price for limit orders
387    pub price: Option<f64>,
388}
389
390/// Authentication request
391#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
392pub struct AuthRequest {
393    /// Grant type
394    pub grant_type: String,
395    /// Client ID
396    pub client_id: String,
397    /// Client secret
398    pub client_secret: String,
399    /// Refresh token (for refresh grant)
400    pub refresh_token: Option<String>,
401    /// Scope
402    pub scope: Option<String>,
403}
404
405impl AuthRequest {
406    /// Create a client credentials authentication request
407    pub fn client_credentials(client_id: String, client_secret: String) -> Self {
408        Self {
409            grant_type: "client_credentials".to_string(),
410            client_id,
411            client_secret,
412            refresh_token: None,
413            scope: None,
414        }
415    }
416
417    /// Create a refresh token authentication request
418    pub fn refresh_token(client_id: String, client_secret: String, refresh_token: String) -> Self {
419        Self {
420            grant_type: "refresh_token".to_string(),
421            client_id,
422            client_secret,
423            refresh_token: Some(refresh_token),
424            scope: None,
425        }
426    }
427}
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432
433    #[test]
434    fn test_fix_new_order_request_market_buy() {
435        let order = fix::NewOrderRequest::market_buy("BTC-PERPETUAL".to_string(), 1.0);
436        assert_eq!(order.symbol, "BTC-PERPETUAL");
437        assert_eq!(order.side, OrderSide::Buy);
438        assert_eq!(order.order_type, OrderType::Market);
439        assert_eq!(order.quantity, 1.0);
440        assert_eq!(order.price, None);
441        assert_eq!(order.time_in_force, TimeInForce::ImmediateOrCancel);
442        assert_eq!(order.client_order_id, None);
443    }
444
445    #[test]
446    fn test_fix_new_order_request_market_sell() {
447        let order = fix::NewOrderRequest::market_sell("ETH-PERPETUAL".to_string(), 2.0);
448        assert_eq!(order.symbol, "ETH-PERPETUAL");
449        assert_eq!(order.side, OrderSide::Sell);
450        assert_eq!(order.order_type, OrderType::Market);
451        assert_eq!(order.quantity, 2.0);
452        assert_eq!(order.price, None);
453        assert_eq!(order.time_in_force, TimeInForce::ImmediateOrCancel);
454    }
455
456    #[test]
457    fn test_fix_new_order_request_limit_buy() {
458        let order = fix::NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0);
459        assert_eq!(order.symbol, "BTC-PERPETUAL");
460        assert_eq!(order.side, OrderSide::Buy);
461        assert_eq!(order.order_type, OrderType::Limit);
462        assert_eq!(order.quantity, 1.0);
463        assert_eq!(order.price, Some(50000.0));
464        assert_eq!(order.time_in_force, TimeInForce::GoodTilCancelled);
465    }
466
467    #[test]
468    fn test_fix_new_order_request_limit_sell() {
469        let order = fix::NewOrderRequest::limit_sell("ETH-PERPETUAL".to_string(), 2.0, 3500.0);
470        assert_eq!(order.symbol, "ETH-PERPETUAL");
471        assert_eq!(order.side, OrderSide::Sell);
472        assert_eq!(order.order_type, OrderType::Limit);
473        assert_eq!(order.quantity, 2.0);
474        assert_eq!(order.price, Some(3500.0));
475        assert_eq!(order.time_in_force, TimeInForce::GoodTilCancelled);
476    }
477
478    #[test]
479    fn test_fix_new_order_request_with_client_order_id() {
480        let order = fix::NewOrderRequest::market_buy("BTC-PERPETUAL".to_string(), 1.0)
481            .with_client_order_id("CLIENT_ORDER_123".to_string());
482        assert_eq!(order.client_order_id, Some("CLIENT_ORDER_123".to_string()));
483    }
484
485    #[test]
486    fn test_fix_new_order_request_with_time_in_force() {
487        let order = fix::NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
488            .with_time_in_force(TimeInForce::FillOrKill);
489        assert_eq!(order.time_in_force, TimeInForce::FillOrKill);
490    }
491
492    #[test]
493    fn test_new_order_request_market_buy() {
494        let order = NewOrderRequest::market_buy("BTC-PERPETUAL".to_string(), 1.0);
495        assert_eq!(order.instrument_name, "BTC-PERPETUAL");
496        assert_eq!(order.amount, 1.0);
497        assert_eq!(order.order_type, OrderType::Market);
498        assert_eq!(order.side, OrderSide::Buy);
499        assert_eq!(order.price, None);
500        assert_eq!(order.time_in_force, TimeInForce::ImmediateOrCancel);
501        assert_eq!(order.post_only, None);
502        assert_eq!(order.reduce_only, None);
503    }
504
505    #[test]
506    fn test_new_order_request_market_sell() {
507        let order = NewOrderRequest::market_sell("ETH-PERPETUAL".to_string(), 2.0);
508        assert_eq!(order.instrument_name, "ETH-PERPETUAL");
509        assert_eq!(order.amount, 2.0);
510        assert_eq!(order.order_type, OrderType::Market);
511        assert_eq!(order.side, OrderSide::Sell);
512        assert_eq!(order.price, None);
513        assert_eq!(order.time_in_force, TimeInForce::ImmediateOrCancel);
514    }
515
516    #[test]
517    fn test_new_order_request_limit_buy() {
518        let order = NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0);
519        assert_eq!(order.instrument_name, "BTC-PERPETUAL");
520        assert_eq!(order.amount, 1.0);
521        assert_eq!(order.order_type, OrderType::Limit);
522        assert_eq!(order.side, OrderSide::Buy);
523        assert_eq!(order.price, Some(50000.0));
524        assert_eq!(order.time_in_force, TimeInForce::GoodTilCancelled);
525    }
526
527    #[test]
528    fn test_new_order_request_limit_sell() {
529        let order = NewOrderRequest::limit_sell("ETH-PERPETUAL".to_string(), 2.0, 3500.0);
530        assert_eq!(order.instrument_name, "ETH-PERPETUAL");
531        assert_eq!(order.amount, 2.0);
532        assert_eq!(order.order_type, OrderType::Limit);
533        assert_eq!(order.side, OrderSide::Sell);
534        assert_eq!(order.price, Some(3500.0));
535        assert_eq!(order.time_in_force, TimeInForce::GoodTilCancelled);
536    }
537
538    #[test]
539    fn test_new_order_request_with_post_only() {
540        let order = NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
541            .with_post_only(true);
542        assert_eq!(order.post_only, Some(true));
543    }
544
545    #[test]
546    fn test_new_order_request_with_reduce_only() {
547        let order = NewOrderRequest::limit_sell("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
548            .with_reduce_only(true);
549        assert_eq!(order.reduce_only, Some(true));
550    }
551
552    #[test]
553    fn test_new_order_request_with_label() {
554        let order = NewOrderRequest::market_buy("BTC-PERPETUAL".to_string(), 1.0)
555            .with_label("test_order".to_string());
556        assert_eq!(order.label, Some("test_order".to_string()));
557    }
558
559    #[test]
560    fn test_new_order_request_with_time_in_force() {
561        let order = NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
562            .with_time_in_force(TimeInForce::FillOrKill);
563        assert_eq!(order.time_in_force, TimeInForce::FillOrKill);
564    }
565
566    #[test]
567    fn test_trigger_type_serialization() {
568        let trigger_types = vec![
569            TriggerType::IndexPrice,
570            TriggerType::MarkPrice,
571            TriggerType::LastPrice,
572        ];
573
574        for trigger_type in trigger_types {
575            let json = serde_json::to_string(&trigger_type).unwrap();
576            let deserialized: TriggerType = serde_json::from_str(&json).unwrap();
577            assert_eq!(trigger_type, deserialized);
578        }
579    }
580
581    #[test]
582    fn test_advanced_order_type_serialization() {
583        let advanced_types = vec![AdvancedOrderType::Usd, AdvancedOrderType::Implv];
584
585        for advanced_type in advanced_types {
586            let json = serde_json::to_string(&advanced_type).unwrap();
587            let deserialized: AdvancedOrderType = serde_json::from_str(&json).unwrap();
588            assert_eq!(advanced_type, deserialized);
589        }
590    }
591
592    #[test]
593    fn test_modify_order_request() {
594        let modify_request = ModifyOrderRequest {
595            order_id: "ORDER_123".to_string(),
596            amount: Some(2.0),
597            price: Some(51000.0),
598            stop_price: None,
599            post_only: Some(true),
600            reduce_only: Some(false),
601            reject_post_only: None,
602            advanced: Some(AdvancedOrderType::Usd),
603        };
604
605        assert_eq!(modify_request.order_id, "ORDER_123");
606        assert_eq!(modify_request.amount, Some(2.0));
607        assert_eq!(modify_request.price, Some(51000.0));
608        assert_eq!(modify_request.post_only, Some(true));
609        assert_eq!(modify_request.advanced, Some(AdvancedOrderType::Usd));
610    }
611
612    #[test]
613    fn test_cancel_order_request() {
614        let cancel_request = CancelOrderRequest {
615            order_id: "ORDER_123".to_string(),
616        };
617        assert_eq!(cancel_request.order_id, "ORDER_123");
618    }
619
620    #[test]
621    fn test_cancel_all_orders_request() {
622        let cancel_all_request = CancelAllOrdersRequest {
623            currency: Some("BTC".to_string()),
624            kind: Some("future".to_string()),
625            instrument_type: Some("perpetual".to_string()),
626        };
627
628        assert_eq!(cancel_all_request.currency, Some("BTC".to_string()));
629        assert_eq!(cancel_all_request.kind, Some("future".to_string()));
630        assert_eq!(
631            cancel_all_request.instrument_type,
632            Some("perpetual".to_string())
633        );
634    }
635
636    #[test]
637    fn test_cancel_all_orders_request_empty() {
638        let cancel_all_request = CancelAllOrdersRequest {
639            currency: None,
640            kind: None,
641            instrument_type: None,
642        };
643
644        assert_eq!(cancel_all_request.currency, None);
645        assert_eq!(cancel_all_request.kind, None);
646        assert_eq!(cancel_all_request.instrument_type, None);
647    }
648
649    #[test]
650    fn test_close_position_request() {
651        let close_request = ClosePositionRequest {
652            instrument_name: "BTC-PERPETUAL".to_string(),
653            order_type: OrderType::Market,
654            price: None,
655        };
656
657        assert_eq!(close_request.instrument_name, "BTC-PERPETUAL");
658        assert_eq!(close_request.order_type, OrderType::Market);
659        assert_eq!(close_request.price, None);
660
661        let close_limit_request = ClosePositionRequest {
662            instrument_name: "ETH-PERPETUAL".to_string(),
663            order_type: OrderType::Limit,
664            price: Some(3500.0),
665        };
666
667        assert_eq!(close_limit_request.price, Some(3500.0));
668    }
669
670    #[test]
671    fn test_auth_request_client_credentials() {
672        let auth_request = AuthRequest::client_credentials(
673            "client_id_123".to_string(),
674            "client_secret_456".to_string(),
675        );
676
677        assert_eq!(auth_request.grant_type, "client_credentials");
678        assert_eq!(auth_request.client_id, "client_id_123");
679        assert_eq!(auth_request.client_secret, "client_secret_456");
680        assert_eq!(auth_request.refresh_token, None);
681        assert_eq!(auth_request.scope, None);
682    }
683
684    #[test]
685    fn test_auth_request_refresh_token() {
686        let auth_request = AuthRequest::refresh_token(
687            "client_id_123".to_string(),
688            "client_secret_456".to_string(),
689            "refresh_token_789".to_string(),
690        );
691
692        assert_eq!(auth_request.grant_type, "refresh_token");
693        assert_eq!(auth_request.client_id, "client_id_123");
694        assert_eq!(auth_request.client_secret, "client_secret_456");
695        assert_eq!(
696            auth_request.refresh_token,
697            Some("refresh_token_789".to_string())
698        );
699        assert_eq!(auth_request.scope, None);
700    }
701
702    #[test]
703    fn test_fix_to_rest_conversion() {
704        let fix_order = fix::NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
705            .with_client_order_id("CLIENT_ORDER_123".to_string());
706
707        let rest_order: NewOrderRequest = fix_order.into();
708
709        assert_eq!(rest_order.instrument_name, "BTC-PERPETUAL");
710        assert_eq!(rest_order.amount, 1.0);
711        assert_eq!(rest_order.order_type, OrderType::Limit);
712        assert_eq!(rest_order.side, OrderSide::Buy);
713        assert_eq!(rest_order.price, Some(50000.0));
714        assert_eq!(
715            rest_order.client_order_id,
716            Some("CLIENT_ORDER_123".to_string())
717        );
718        assert_eq!(rest_order.post_only, None);
719        assert_eq!(rest_order.reduce_only, None);
720    }
721
722    #[test]
723    fn test_rest_to_fix_conversion() {
724        let rest_order = NewOrderRequest::limit_sell("ETH-PERPETUAL".to_string(), 2.0, 3500.0)
725            .with_label("test_order".to_string())
726            .with_post_only(true);
727
728        let fix_order: fix::NewOrderRequest = rest_order.into();
729
730        assert_eq!(fix_order.symbol, "ETH-PERPETUAL");
731        assert_eq!(fix_order.quantity, 2.0);
732        assert_eq!(fix_order.order_type, OrderType::Limit);
733        assert_eq!(fix_order.side, OrderSide::Sell);
734        assert_eq!(fix_order.price, Some(3500.0));
735        assert_eq!(fix_order.time_in_force, TimeInForce::GoodTilCancelled);
736    }
737
738    #[test]
739    fn test_serialization_roundtrip() {
740        let order = NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
741            .with_post_only(true)
742            .with_label("test_order".to_string());
743
744        let json = serde_json::to_string(&order).unwrap();
745        let deserialized: NewOrderRequest = serde_json::from_str(&json).unwrap();
746
747        assert_eq!(order.instrument_name, deserialized.instrument_name);
748        assert_eq!(order.amount, deserialized.amount);
749        assert_eq!(order.order_type, deserialized.order_type);
750        assert_eq!(order.side, deserialized.side);
751        assert_eq!(order.price, deserialized.price);
752        assert_eq!(order.post_only, deserialized.post_only);
753        assert_eq!(order.label, deserialized.label);
754    }
755
756    #[test]
757    fn test_debug_and_display_implementations() {
758        let order = NewOrderRequest::market_buy("BTC-PERPETUAL".to_string(), 1.0);
759        let debug_str = format!("{:?}", order);
760        let display_str = format!("{}", order);
761
762        assert!(debug_str.contains("BTC-PERPETUAL"));
763        assert!(display_str.contains("BTC-PERPETUAL"));
764
765        let auth_request =
766            AuthRequest::client_credentials("client_id".to_string(), "client_secret".to_string());
767        let auth_debug = format!("{:?}", auth_request);
768        let auth_display = format!("{}", auth_request);
769
770        assert!(auth_debug.contains("client_credentials"));
771        assert!(auth_display.contains("client_credentials"));
772    }
773
774    #[test]
775    fn test_enum_equality_and_cloning() {
776        let trigger1 = TriggerType::IndexPrice;
777        let trigger2 = trigger1.clone();
778        assert_eq!(trigger1, trigger2);
779
780        let advanced1 = AdvancedOrderType::Usd;
781        let advanced2 = advanced1.clone();
782        assert_eq!(advanced1, advanced2);
783    }
784
785    #[test]
786    fn test_complex_order_with_all_fields() {
787        let order = NewOrderRequest {
788            instrument_name: "BTC-PERPETUAL".to_string(),
789            amount: 1.5,
790            order_type: OrderType::StopLimit,
791            side: OrderSide::Buy,
792            price: Some(50000.0),
793            time_in_force: TimeInForce::GoodTilDay,
794            post_only: Some(true),
795            reduce_only: Some(false),
796            label: Some("complex_order".to_string()),
797            stop_price: Some(49000.0),
798            trigger: Some(TriggerType::MarkPrice),
799            advanced: Some(AdvancedOrderType::Implv),
800            max_show: Some(0.5),
801            reject_post_only: Some(false),
802            valid_until: Some(1640995200000),
803            client_order_id: Some("CLIENT_ORDER_COMPLEX".to_string()),
804        };
805
806        assert_eq!(order.instrument_name, "BTC-PERPETUAL");
807        assert_eq!(order.amount, 1.5);
808        assert_eq!(order.order_type, OrderType::StopLimit);
809        assert_eq!(order.stop_price, Some(49000.0));
810        assert_eq!(order.trigger, Some(TriggerType::MarkPrice));
811        assert_eq!(order.advanced, Some(AdvancedOrderType::Implv));
812        assert_eq!(order.max_show, Some(0.5));
813        assert_eq!(order.valid_until, Some(1640995200000));
814    }
815}