deribit_fix/message/orders/
cancel_request.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 22/7/25
5******************************************************************************/
6
7//! Order Cancel Request FIX Message Implementation
8
9use crate::error::{DeribitFixError, Result as DeribitFixResult};
10use crate::{message::builder::MessageBuilder, model::types::MsgType};
11use chrono::Utc;
12use deribit_base::{impl_json_debug_pretty, impl_json_display};
13use serde::{Deserialize, Serialize};
14
15/// Order Cancel Request message (MsgType = 'F')
16#[derive(Clone, PartialEq, Serialize, Deserialize)]
17pub struct OrderCancelRequest {
18    /// Original order identifier assigned by the user (optional)
19    pub cl_ord_id: Option<String>,
20    /// Order identifier assigned by Deribit (optional)
21    pub orig_cl_ord_id: Option<String>,
22    /// Custom label for order (optional)
23    pub deribit_label: Option<String>,
24    /// Instrument symbol (required if OrigClOrdId is absent)
25    pub symbol: Option<String>,
26    /// Currency to speed up search
27    pub currency: Option<String>,
28}
29
30impl OrderCancelRequest {
31    /// Create cancel request by original client order ID
32    pub fn by_orig_cl_ord_id(orig_cl_ord_id: String) -> Self {
33        Self {
34            cl_ord_id: None,
35            orig_cl_ord_id: Some(orig_cl_ord_id),
36            deribit_label: None,
37            symbol: None,
38            currency: None,
39        }
40    }
41
42    /// Create cancel request by client order ID
43    pub fn by_cl_ord_id(cl_ord_id: String, symbol: String) -> Self {
44        Self {
45            cl_ord_id: Some(cl_ord_id),
46            orig_cl_ord_id: None,
47            deribit_label: None,
48            symbol: Some(symbol),
49            currency: None,
50        }
51    }
52
53    /// Create cancel request by Deribit label
54    pub fn by_deribit_label(deribit_label: String, symbol: String) -> Self {
55        Self {
56            cl_ord_id: None,
57            orig_cl_ord_id: None,
58            deribit_label: Some(deribit_label),
59            symbol: Some(symbol),
60            currency: None,
61        }
62    }
63
64    /// Set currency for faster search
65    pub fn with_currency(mut self, currency: String) -> Self {
66        self.currency = Some(currency);
67        self
68    }
69
70    /// Convert to FIX message
71    pub fn to_fix_message(
72        &self,
73        sender_comp_id: &str,
74        target_comp_id: &str,
75        msg_seq_num: u32,
76    ) -> DeribitFixResult<String> {
77        let mut builder = MessageBuilder::new()
78            .msg_type(MsgType::OrderCancelRequest)
79            .sender_comp_id(sender_comp_id.to_string())
80            .target_comp_id(target_comp_id.to_string())
81            .msg_seq_num(msg_seq_num)
82            .sending_time(Utc::now());
83
84        // At least one identifier must be present
85        if self.cl_ord_id.is_none() && self.orig_cl_ord_id.is_none() && self.deribit_label.is_none()
86        {
87            return Err(DeribitFixError::Generic(
88                "Either OrigClOrdId or ClOrdId must be specified".to_string(),
89            ));
90        }
91
92        if let Some(cl_ord_id) = &self.cl_ord_id {
93            builder = builder.field(11, cl_ord_id.clone());
94        }
95
96        if let Some(orig_cl_ord_id) = &self.orig_cl_ord_id {
97            builder = builder.field(41, orig_cl_ord_id.clone());
98        }
99
100        if let Some(deribit_label) = &self.deribit_label {
101            builder = builder.field(100010, deribit_label.clone());
102        }
103
104        if let Some(symbol) = &self.symbol {
105            builder = builder.field(55, symbol.clone());
106        }
107
108        if let Some(currency) = &self.currency {
109            builder = builder.field(15, currency.clone());
110        }
111
112        Ok(builder.build()?.to_string())
113    }
114}
115
116impl_json_display!(OrderCancelRequest);
117impl_json_debug_pretty!(OrderCancelRequest);
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_order_cancel_request_by_orig_cl_ord_id() {
125        let cancel_request = OrderCancelRequest::by_orig_cl_ord_id("ORIG123".to_string());
126
127        assert_eq!(cancel_request.orig_cl_ord_id, Some("ORIG123".to_string()));
128        assert_eq!(cancel_request.cl_ord_id, None);
129        assert_eq!(cancel_request.deribit_label, None);
130        assert_eq!(cancel_request.symbol, None);
131    }
132
133    #[test]
134    fn test_order_cancel_request_by_cl_ord_id() {
135        let cancel_request =
136            OrderCancelRequest::by_cl_ord_id("ORDER123".to_string(), "BTC-PERPETUAL".to_string());
137
138        assert_eq!(cancel_request.cl_ord_id, Some("ORDER123".to_string()));
139        assert_eq!(cancel_request.symbol, Some("BTC-PERPETUAL".to_string()));
140        assert_eq!(cancel_request.orig_cl_ord_id, None);
141        assert_eq!(cancel_request.deribit_label, None);
142    }
143
144    #[test]
145    fn test_order_cancel_request_by_deribit_label() {
146        let cancel_request = OrderCancelRequest::by_deribit_label(
147            "my-order".to_string(),
148            "BTC-PERPETUAL".to_string(),
149        );
150
151        assert_eq!(cancel_request.deribit_label, Some("my-order".to_string()));
152        assert_eq!(cancel_request.symbol, Some("BTC-PERPETUAL".to_string()));
153        assert_eq!(cancel_request.cl_ord_id, None);
154        assert_eq!(cancel_request.orig_cl_ord_id, None);
155    }
156
157    #[test]
158    fn test_order_cancel_request_with_currency() {
159        let cancel_request =
160            OrderCancelRequest::by_cl_ord_id("ORDER123".to_string(), "BTC-PERPETUAL".to_string())
161                .with_currency("BTC".to_string());
162
163        assert_eq!(cancel_request.currency, Some("BTC".to_string()));
164    }
165
166    #[test]
167    fn test_order_cancel_request_to_fix_message() {
168        let cancel_request = OrderCancelRequest::by_orig_cl_ord_id("ORIG123".to_string());
169
170        let fix_message = cancel_request.to_fix_message("CLIENT", "DERIBITSERVER", 1);
171        assert!(fix_message.is_ok());
172
173        let message = fix_message.unwrap();
174        assert!(message.contains("35=F")); // MsgType = OrderCancelRequest
175        assert!(message.contains("41=ORIG123")); // OrigClOrdId
176    }
177
178    #[test]
179    fn test_order_cancel_request_validation_error() {
180        let cancel_request = OrderCancelRequest {
181            cl_ord_id: None,
182            orig_cl_ord_id: None,
183            deribit_label: None,
184            symbol: None,
185            currency: None,
186        };
187
188        let fix_message = cancel_request.to_fix_message("CLIENT", "DERIBITSERVER", 1);
189        assert!(fix_message.is_err());
190    }
191}