Skip to main content

longbridge/trade/requests/
submit_multileg.rs

1use rust_decimal::Decimal;
2use serde::Serialize;
3use time::OffsetDateTime;
4
5use crate::trade::{MultiLegStrategy, OrderSide, OrderType};
6
7/// A leg of a multi-leg combination order to submit
8#[derive(Debug, Serialize, Clone)]
9pub struct SubmitMultiLegOrderLeg {
10    /// Option symbol, in `ticker.region` format (e.g. `QQQ260731C764000.US`)
11    symbol: String,
12    /// Leg ratio quantity — must be a positive number.  The direction of each
13    /// leg is implied by `strategy` together with the order `side`, not by the
14    /// sign of this value; a negative or zero ratio is rejected by the server
15    /// with `602001`.
16    ratio_quantity: Decimal,
17}
18
19impl SubmitMultiLegOrderLeg {
20    /// Create a new `SubmitMultiLegOrderLeg`
21    #[inline]
22    pub fn new(symbol: impl Into<String>, ratio_quantity: Decimal) -> Self {
23        Self {
24            symbol: symbol.into(),
25            ratio_quantity,
26        }
27    }
28}
29
30/// Options for submit multi-leg order request
31#[derive(Debug, Serialize, Clone)]
32pub struct SubmitMultiLegOrderOptions {
33    side: OrderSide,
34    order_type: OrderType,
35    submitted_quantity: Decimal,
36    strategy: MultiLegStrategy,
37    legs: Vec<SubmitMultiLegOrderLeg>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    submitted_price: Option<Decimal>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    remark: Option<String>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    client_request_id: Option<String>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    delayed_until: Option<i64>,
46}
47
48impl SubmitMultiLegOrderOptions {
49    /// Create a new `SubmitMultiLegOrderOptions`
50    #[inline]
51    pub fn new(
52        side: OrderSide,
53        order_type: OrderType,
54        submitted_quantity: Decimal,
55        strategy: MultiLegStrategy,
56        legs: impl IntoIterator<Item = SubmitMultiLegOrderLeg>,
57    ) -> Self {
58        Self {
59            side,
60            order_type,
61            submitted_quantity,
62            strategy,
63            legs: legs.into_iter().collect(),
64            submitted_price: None,
65            remark: None,
66            client_request_id: None,
67            delayed_until: None,
68        }
69    }
70
71    /// Set the submitted price
72    ///
73    /// Required for limit order types such as `LO`.
74    #[inline]
75    #[must_use]
76    pub fn submitted_price(self, submitted_price: Decimal) -> Self {
77        Self {
78            submitted_price: Some(submitted_price),
79            ..self
80        }
81    }
82
83    /// Set the remark
84    #[inline]
85    #[must_use]
86    pub fn remark(self, remark: impl Into<String>) -> Self {
87        Self {
88            remark: Some(remark.into()),
89            ..self
90        }
91    }
92
93    /// Set the client request ID for idempotency control.
94    /// If not specified, idempotency control is skipped.
95    /// The server caches this ID for 10 minutes; requests with the same ID
96    /// within that period return the original response without creating a new
97    /// order.
98    #[inline]
99    #[must_use]
100    pub fn client_request_id(self, id: impl Into<String>) -> Self {
101        Self {
102            client_request_id: Some(id.into()),
103            ..self
104        }
105    }
106
107    /// Set the time-condition submit time.
108    ///
109    /// The order is held in `DelayedNotReported` status and only submitted at
110    /// this time, which must be later than now and no more than 90 days ahead.
111    /// Only `LO` / `MO` support time conditions.
112    #[inline]
113    #[must_use]
114    pub fn delayed_until(self, delayed_until: OffsetDateTime) -> Self {
115        Self {
116            delayed_until: Some(super::delayed_until_timestamp(delayed_until)),
117            ..self
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use time::macros::datetime;
125
126    use super::*;
127
128    #[test]
129    fn delayed_until() {
130        let opts = SubmitMultiLegOrderOptions::new(
131            OrderSide::Buy,
132            OrderType::MO,
133            Decimal::from(1),
134            MultiLegStrategy::Straddle,
135            [],
136        );
137        assert!(
138            serde_json::to_value(&opts)
139                .unwrap()
140                .get("delayed_until")
141                .is_none()
142        );
143
144        let opts = opts.delayed_until(datetime!(2026-01-01 00:00 UTC));
145        assert_eq!(
146            serde_json::to_value(&opts).unwrap()["delayed_until"],
147            serde_json::json!(1767225600)
148        );
149    }
150}