deribit_fix/message/orders/
cancel_replace_request.rs1use super::*;
10use crate::error::Result as DeribitFixResult;
11use crate::message::builder::MessageBuilder;
12use crate::model::types::MsgType;
13use chrono::{DateTime, Utc};
14use serde::{Deserialize, Serialize};
15
16#[derive(Clone, PartialEq, Serialize, Deserialize)]
18pub struct OrderCancelReplaceRequest {
19 pub orig_cl_ord_id: String,
21 pub cl_ord_id: String,
23 pub symbol: String,
25 pub side: OrderSide,
27 pub transact_time: DateTime<Utc>,
29 pub order_qty: Option<f64>,
31 pub price: Option<f64>,
33 pub ord_type: Option<OrderType>,
35 pub time_in_force: Option<TimeInForce>,
37 pub stop_px: Option<f64>,
39 pub display_qty: Option<f64>,
41 pub qty_type: Option<QuantityType>,
43 pub deribit_label: Option<String>,
45 pub deribit_mm_protection: Option<bool>,
47}
48
49impl OrderCancelReplaceRequest {
50 pub fn new(orig_cl_ord_id: String, cl_ord_id: String, symbol: String, side: OrderSide) -> Self {
52 Self {
53 orig_cl_ord_id,
54 cl_ord_id,
55 symbol,
56 side,
57 transact_time: Utc::now(),
58 order_qty: None,
59 price: None,
60 ord_type: None,
61 time_in_force: None,
62 stop_px: None,
63 display_qty: None,
64 qty_type: None,
65 deribit_label: None,
66 deribit_mm_protection: None,
67 }
68 }
69
70 pub fn with_qty(mut self, qty: f64) -> Self {
72 self.order_qty = Some(qty);
73 self
74 }
75
76 pub fn with_price(mut self, price: f64) -> Self {
78 self.price = Some(price);
79 self
80 }
81
82 pub fn with_order_type(mut self, ord_type: OrderType) -> Self {
84 self.ord_type = Some(ord_type);
85 self
86 }
87
88 pub fn with_time_in_force(mut self, tif: TimeInForce) -> Self {
90 self.time_in_force = Some(tif);
91 self
92 }
93
94 pub fn with_stop_price(mut self, stop_px: f64) -> Self {
96 self.stop_px = Some(stop_px);
97 self
98 }
99
100 pub fn with_display_qty(mut self, display_qty: f64) -> Self {
102 self.display_qty = Some(display_qty);
103 self
104 }
105
106 pub fn with_qty_type(mut self, qty_type: QuantityType) -> Self {
108 self.qty_type = Some(qty_type);
109 self
110 }
111
112 pub fn with_label(mut self, label: String) -> Self {
114 self.deribit_label = Some(label);
115 self
116 }
117
118 pub fn with_mmp(mut self, enabled: bool) -> Self {
120 self.deribit_mm_protection = Some(enabled);
121 self
122 }
123
124 pub fn to_fix_message(
126 &self,
127 sender_comp_id: &str,
128 target_comp_id: &str,
129 msg_seq_num: u32,
130 ) -> DeribitFixResult<String> {
131 let mut builder = MessageBuilder::new()
132 .msg_type(MsgType::OrderCancelReplaceRequest)
133 .sender_comp_id(sender_comp_id.to_string())
134 .target_comp_id(target_comp_id.to_string())
135 .msg_seq_num(msg_seq_num)
136 .sending_time(Utc::now());
137
138 builder = builder
140 .field(41, self.orig_cl_ord_id.clone()) .field(11, self.cl_ord_id.clone()) .field(55, self.symbol.clone()) .field(54, char::from(self.side).to_string()) .field(
145 60,
146 self.transact_time.format("%Y%m%d-%H:%M:%S%.3f").to_string(),
147 ); if let Some(order_qty) = &self.order_qty {
151 builder = builder.field(38, order_qty.to_string());
152 }
153
154 if let Some(price) = &self.price {
155 builder = builder.field(44, price.to_string());
156 }
157
158 if let Some(ord_type) = &self.ord_type {
159 builder = builder.field(40, char::from(*ord_type).to_string());
160 }
161
162 if let Some(time_in_force) = &self.time_in_force {
163 builder = builder.field(59, char::from(*time_in_force).to_string());
164 }
165
166 if let Some(stop_px) = &self.stop_px {
167 builder = builder.field(99, stop_px.to_string());
168 }
169
170 if let Some(display_qty) = &self.display_qty {
171 builder = builder.field(1138, display_qty.to_string());
172 }
173
174 if let Some(qty_type) = &self.qty_type {
175 builder = builder.field(854, i32::from(*qty_type).to_string());
176 }
177
178 if let Some(deribit_label) = &self.deribit_label {
179 builder = builder.field(100010, deribit_label.clone());
180 }
181
182 if let Some(deribit_mm_protection) = &self.deribit_mm_protection {
183 builder = builder.field(
184 9008,
185 if *deribit_mm_protection { "Y" } else { "N" }.to_string(),
186 );
187 }
188
189 Ok(builder.build()?.to_string())
190 }
191}
192
193impl_json_display!(OrderCancelReplaceRequest);
194impl_json_debug_pretty!(OrderCancelReplaceRequest);
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_cancel_replace_request_creation() {
202 let request = OrderCancelReplaceRequest::new(
203 "ORIG123".to_string(),
204 "NEW123".to_string(),
205 "BTC-PERPETUAL".to_string(),
206 OrderSide::Buy,
207 );
208
209 assert_eq!(request.orig_cl_ord_id, "ORIG123");
210 assert_eq!(request.cl_ord_id, "NEW123");
211 assert_eq!(request.symbol, "BTC-PERPETUAL");
212 assert_eq!(request.side, OrderSide::Buy);
213 assert!(request.order_qty.is_none());
214 assert!(request.price.is_none());
215 assert!(request.ord_type.is_none());
216 }
217
218 #[test]
219 fn test_cancel_replace_request_with_quantity_and_price() {
220 let request = OrderCancelReplaceRequest::new(
221 "ORIG123".to_string(),
222 "NEW123".to_string(),
223 "BTC-PERPETUAL".to_string(),
224 OrderSide::Sell,
225 )
226 .with_qty(15.0)
227 .with_price(48000.0);
228
229 assert_eq!(request.order_qty, Some(15.0));
230 assert_eq!(request.price, Some(48000.0));
231 }
232
233 #[test]
234 fn test_cancel_replace_request_with_all_options() {
235 let request = OrderCancelReplaceRequest::new(
236 "ORIG123".to_string(),
237 "NEW123".to_string(),
238 "ETH-PERPETUAL".to_string(),
239 OrderSide::Buy,
240 )
241 .with_qty(20.0)
242 .with_price(3200.0)
243 .with_order_type(OrderType::Limit)
244 .with_time_in_force(TimeInForce::GoodTillCancelled)
245 .with_stop_price(3100.0)
246 .with_display_qty(10.0)
247 .with_qty_type(QuantityType::Contracts)
248 .with_label("updated-order".to_string())
249 .with_mmp(true);
250
251 assert_eq!(request.order_qty, Some(20.0));
252 assert_eq!(request.price, Some(3200.0));
253 assert_eq!(request.ord_type, Some(OrderType::Limit));
254 assert_eq!(request.time_in_force, Some(TimeInForce::GoodTillCancelled));
255 assert_eq!(request.stop_px, Some(3100.0));
256 assert_eq!(request.display_qty, Some(10.0));
257 assert_eq!(request.qty_type, Some(QuantityType::Contracts));
258 assert_eq!(request.deribit_label, Some("updated-order".to_string()));
259 assert_eq!(request.deribit_mm_protection, Some(true));
260 }
261
262 #[test]
263 fn test_cancel_replace_request_to_fix_message() {
264 let request = OrderCancelReplaceRequest::new(
265 "ORIG123".to_string(),
266 "NEW123".to_string(),
267 "BTC-PERPETUAL".to_string(),
268 OrderSide::Buy,
269 )
270 .with_qty(10.0)
271 .with_price(51000.0)
272 .with_order_type(OrderType::Limit);
273
274 let fix_message = request.to_fix_message("SENDER", "TARGET", 1).unwrap();
275
276 assert!(fix_message.contains("35=G")); assert!(fix_message.contains("41=ORIG123")); assert!(fix_message.contains("11=NEW123")); assert!(fix_message.contains("55=BTC-PERPETUAL")); assert!(fix_message.contains("54=1")); assert!(fix_message.contains("38=10")); assert!(fix_message.contains("44=51000")); assert!(fix_message.contains("40=2")); }
286
287 #[test]
288 fn test_cancel_replace_request_minimal_fix_message() {
289 let request = OrderCancelReplaceRequest::new(
290 "ORIG456".to_string(),
291 "NEW456".to_string(),
292 "ETH-PERPETUAL".to_string(),
293 OrderSide::Sell,
294 );
295
296 let fix_message = request.to_fix_message("SENDER", "TARGET", 2).unwrap();
297
298 assert!(fix_message.contains("35=G")); assert!(fix_message.contains("41=ORIG456")); assert!(fix_message.contains("11=NEW456")); assert!(fix_message.contains("55=ETH-PERPETUAL")); assert!(fix_message.contains("54=2")); assert!(!fix_message.contains("38=")); assert!(!fix_message.contains("44=")); assert!(!fix_message.contains("40=")); }
310
311 #[test]
312 fn test_cancel_replace_request_with_label_and_mmp() {
313 let request = OrderCancelReplaceRequest::new(
314 "ORIG789".to_string(),
315 "NEW789".to_string(),
316 "BTC-PERPETUAL".to_string(),
317 OrderSide::Buy,
318 )
319 .with_label("strategy-v2".to_string())
320 .with_mmp(false);
321
322 let fix_message = request.to_fix_message("SENDER", "TARGET", 3).unwrap();
323
324 assert!(fix_message.contains("100010=strategy-v2")); assert!(fix_message.contains("9008=N")); }
327}