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