Skip to main content

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