longbridge/trade/requests/
replace_order.rs1use rust_decimal::Decimal;
2use serde::Serialize;
3use time::OffsetDateTime;
4
5use crate::trade::{AttachedOrderType, OrderType, OutsideRTH, TimeInForceType};
6
7#[derive(Debug, Serialize, Clone)]
9pub struct ReplaceOrderOptions {
10 order_id: String,
11 quantity: Decimal,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 price: Option<Decimal>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 trigger_price: Option<Decimal>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 limit_offset: Option<Decimal>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 trailing_amount: Option<Decimal>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 trailing_percent: Option<Decimal>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 limit_depth_level: Option<i32>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 trigger_count: Option<i32>,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 monitor_price: Option<Decimal>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 remark: Option<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 delayed_until: Option<i64>,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 attached_params: Option<ReplaceAttachedParams>,
34}
35
36impl ReplaceOrderOptions {
37 #[inline]
39 pub fn new(order_id: impl Into<String>, quantity: Decimal) -> Self {
40 Self {
41 order_id: order_id.into(),
42 quantity,
43 price: None,
44 trigger_price: None,
45 limit_offset: None,
46 trailing_amount: None,
47 trailing_percent: None,
48 limit_depth_level: None,
49 trigger_count: None,
50 monitor_price: None,
51 remark: None,
52 delayed_until: None,
53 attached_params: None,
54 }
55 }
56
57 #[inline]
59 #[must_use]
60 pub fn price(self, price: Decimal) -> Self {
61 Self {
62 price: Some(price),
63 ..self
64 }
65 }
66
67 #[inline]
69 #[must_use]
70 pub fn trigger_price(self, trigger_price: Decimal) -> Self {
71 Self {
72 trigger_price: Some(trigger_price),
73 ..self
74 }
75 }
76
77 #[inline]
79 #[must_use]
80 pub fn limit_offset(self, limit_offset: Decimal) -> Self {
81 Self {
82 limit_offset: Some(limit_offset),
83 ..self
84 }
85 }
86
87 #[inline]
89 #[must_use]
90 pub fn trailing_amount(self, trailing_amount: Decimal) -> Self {
91 Self {
92 trailing_amount: Some(trailing_amount),
93 ..self
94 }
95 }
96
97 #[inline]
99 #[must_use]
100 pub fn trailing_percent(self, trailing_percent: Decimal) -> Self {
101 Self {
102 trailing_percent: Some(trailing_percent),
103 ..self
104 }
105 }
106
107 #[inline]
109 #[must_use]
110 pub fn limit_depth_level(self, limit_depth_level: i32) -> Self {
111 Self {
112 limit_depth_level: Some(limit_depth_level),
113 ..self
114 }
115 }
116
117 #[inline]
119 #[must_use]
120 pub fn trigger_count(self, trigger_count: i32) -> Self {
121 Self {
122 trigger_count: Some(trigger_count),
123 ..self
124 }
125 }
126
127 #[inline]
129 #[must_use]
130 pub fn monitor_price(self, monitor_price: Decimal) -> Self {
131 Self {
132 monitor_price: Some(monitor_price),
133 ..self
134 }
135 }
136
137 #[inline]
139 #[must_use]
140 pub fn remark(self, remark: impl Into<String>) -> Self {
141 Self {
142 remark: Some(remark.into()),
143 ..self
144 }
145 }
146
147 #[inline]
153 #[must_use]
154 pub fn delayed_until(self, delayed_until: OffsetDateTime) -> Self {
155 Self {
156 delayed_until: Some(super::delayed_until_timestamp(delayed_until)),
157 ..self
158 }
159 }
160
161 #[inline]
166 #[must_use]
167 pub fn clear_delayed_until(self) -> Self {
168 Self {
169 delayed_until: Some(1),
170 ..self
171 }
172 }
173
174 pub fn attached_params(self, params: ReplaceAttachedParams) -> Self {
176 Self {
177 attached_params: Some(params),
178 ..self
179 }
180 }
181}
182
183#[derive(Debug, Serialize, Clone)]
185pub struct ReplaceAttachedParams {
186 attached_order_type: AttachedOrderType,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 profit_taker_price: Option<Decimal>,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 stop_loss_price: Option<Decimal>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 time_in_force: Option<TimeInForceType>,
193 #[serde(skip_serializing_if = "Option::is_none")]
194 expire_time: Option<i64>,
195 #[serde(skip_serializing_if = "Option::is_none")]
196 activate_order_type: Option<OrderType>,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 profit_taker_submit_price: Option<Decimal>,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 stop_loss_submit_price: Option<Decimal>,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 activate_rth: Option<OutsideRTH>,
203 #[serde(skip_serializing_if = "Option::is_none")]
204 profit_taker_id: Option<i64>,
205 #[serde(skip_serializing_if = "Option::is_none")]
206 stop_loss_id: Option<i64>,
207 #[serde(skip_serializing_if = "Option::is_none")]
208 cancel_all_attached: Option<bool>,
209 #[serde(skip_serializing_if = "Option::is_none")]
210 main_id: Option<i64>,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 quantity: Option<Decimal>,
213 #[serde(skip_serializing_if = "Option::is_none")]
214 market_price: Option<Decimal>,
215 #[serde(skip_serializing_if = "Option::is_none")]
216 profit_taker_depth_level: Option<i32>,
217 #[serde(skip_serializing_if = "Option::is_none")]
218 stop_loss_depth_level: Option<i32>,
219}
220
221impl ReplaceAttachedParams {
222 pub fn new(attached_order_type: AttachedOrderType) -> Self {
224 Self {
225 attached_order_type,
226 profit_taker_price: None,
227 stop_loss_price: None,
228 time_in_force: None,
229 expire_time: None,
230 activate_order_type: None,
231 profit_taker_submit_price: None,
232 stop_loss_submit_price: None,
233 activate_rth: None,
234 profit_taker_id: None,
235 stop_loss_id: None,
236 cancel_all_attached: None,
237 main_id: None,
238 quantity: None,
239 market_price: None,
240 profit_taker_depth_level: None,
241 stop_loss_depth_level: None,
242 }
243 }
244 pub fn profit_taker_price(self, v: Decimal) -> Self {
246 Self {
247 profit_taker_price: Some(v),
248 ..self
249 }
250 }
251 pub fn stop_loss_price(self, v: Decimal) -> Self {
253 Self {
254 stop_loss_price: Some(v),
255 ..self
256 }
257 }
258 pub fn time_in_force(self, v: TimeInForceType) -> Self {
260 Self {
261 time_in_force: Some(v),
262 ..self
263 }
264 }
265 pub fn expire_time(self, v: i64) -> Self {
267 Self {
268 expire_time: Some(v),
269 ..self
270 }
271 }
272 pub fn activate_order_type(self, v: OrderType) -> Self {
274 Self {
275 activate_order_type: Some(v),
276 ..self
277 }
278 }
279 pub fn profit_taker_submit_price(self, v: Decimal) -> Self {
281 Self {
282 profit_taker_submit_price: Some(v),
283 ..self
284 }
285 }
286 pub fn stop_loss_submit_price(self, v: Decimal) -> Self {
288 Self {
289 stop_loss_submit_price: Some(v),
290 ..self
291 }
292 }
293 pub fn activate_rth(self, v: OutsideRTH) -> Self {
295 Self {
296 activate_rth: Some(v),
297 ..self
298 }
299 }
300
301 pub fn profit_taker_depth_level(self, v: i32) -> Self {
304 Self {
305 profit_taker_depth_level: Some(v),
306 ..self
307 }
308 }
309
310 pub fn stop_loss_depth_level(self, v: i32) -> Self {
313 Self {
314 stop_loss_depth_level: Some(v),
315 ..self
316 }
317 }
318 pub fn profit_taker_id(self, v: i64) -> Self {
320 Self {
321 profit_taker_id: Some(v),
322 ..self
323 }
324 }
325 pub fn stop_loss_id(self, v: i64) -> Self {
327 Self {
328 stop_loss_id: Some(v),
329 ..self
330 }
331 }
332 pub fn cancel_all_attached(self) -> Self {
334 Self {
335 cancel_all_attached: Some(true),
336 ..self
337 }
338 }
339 pub fn main_id(self, v: i64) -> Self {
341 Self {
342 main_id: Some(v),
343 ..self
344 }
345 }
346 pub fn quantity(self, v: Decimal) -> Self {
348 Self {
349 quantity: Some(v),
350 ..self
351 }
352 }
353 pub fn market_price(self, v: Decimal) -> Self {
355 Self {
356 market_price: Some(v),
357 ..self
358 }
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use time::macros::datetime;
365
366 use super::*;
367
368 #[test]
369 fn delayed_until() {
370 let opts = ReplaceOrderOptions::new("1", Decimal::from(100));
371 assert!(
372 serde_json::to_value(&opts)
373 .unwrap()
374 .get("delayed_until")
375 .is_none()
376 );
377
378 let opts = opts.delayed_until(datetime!(2026-01-01 00:00 UTC));
379 assert_eq!(
380 serde_json::to_value(&opts).unwrap()["delayed_until"],
381 serde_json::json!(1767225600)
382 );
383
384 let opts = opts.delayed_until(OffsetDateTime::UNIX_EPOCH + time::Duration::SECOND);
385 assert_eq!(
386 serde_json::to_value(&opts).unwrap()["delayed_until"],
387 serde_json::json!(2),
388 "a time that maps to the clear sentinel must not be sent as 1"
389 );
390
391 let opts = opts.clear_delayed_until();
392 assert_eq!(
393 serde_json::to_value(&opts).unwrap()["delayed_until"],
394 serde_json::json!(1)
395 );
396
397 let opts = opts.delayed_until(datetime!(2026-01-02 00:00 UTC));
399 assert_eq!(
400 serde_json::to_value(&opts).unwrap()["delayed_until"],
401 serde_json::json!(1767312000)
402 );
403 }
404
405 #[test]
406 fn attached_params_depth_level() {
407 let params = ReplaceAttachedParams::new(AttachedOrderType::Bracket);
408 let value = serde_json::to_value(¶ms).unwrap();
409 assert!(value.get("profit_taker_depth_level").is_none());
410 assert!(value.get("stop_loss_depth_level").is_none());
411
412 let params = params.profit_taker_depth_level(2).stop_loss_depth_level(5);
413 let value = serde_json::to_value(¶ms).unwrap();
414 assert_eq!(value["profit_taker_depth_level"], serde_json::json!(2));
415 assert_eq!(value["stop_loss_depth_level"], serde_json::json!(5));
416 }
417}