Skip to main content

rust_okx/ws/request/
spread.rs

1//! Spread Trading WebSocket trade request models.
2
3use serde::Serialize;
4
5/// Place-spread-order request body (`sprd-order`).
6///
7/// OKX docs: <https://www.okx.com/docs-v5/en/#spread-trading-websocket-trade-api-ws-place-order>
8#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
9#[serde(rename_all = "camelCase")]
10#[non_exhaustive]
11pub struct PlaceSpreadOrderRequest {
12    /// Spread ID.
13    pub sprd_id: String,
14    /// Client order ID.
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub cl_ord_id: Option<String>,
17    /// Order tag.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub tag: Option<String>,
20    /// Order side: `buy` or `sell`.
21    pub side: String,
22    /// Spread order type: `limit`, `post_only`, or `ioc`.
23    pub ord_type: String,
24    /// Order quantity.
25    pub sz: String,
26    /// Order price.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub px: Option<String>,
29}
30
31impl PlaceSpreadOrderRequest {
32    /// Create a spread-order request with the documented required fields.
33    pub fn new(
34        sprd_id: impl Into<String>,
35        side: impl Into<String>,
36        ord_type: impl Into<String>,
37        size: impl Into<String>,
38    ) -> Self {
39        Self {
40            sprd_id: sprd_id.into(),
41            cl_ord_id: None,
42            tag: None,
43            side: side.into(),
44            ord_type: ord_type.into(),
45            sz: size.into(),
46            px: None,
47        }
48    }
49
50    /// Set the client order ID.
51    pub fn client_order_id(mut self, cl_ord_id: impl Into<String>) -> Self {
52        self.cl_ord_id = Some(cl_ord_id.into());
53        self
54    }
55
56    /// Set the order tag.
57    pub fn tag(mut self, tag: impl Into<String>) -> Self {
58        self.tag = Some(tag.into());
59        self
60    }
61
62    /// Set the order price.
63    pub fn price(mut self, price: impl Into<String>) -> Self {
64        self.px = Some(price.into());
65        self
66    }
67}
68
69/// Amend-spread-order request body (`sprd-amend-order`).
70///
71/// Either `ordId` or `clOrdId` is required; when both are supplied OKX uses
72/// `ordId`. At least one of `newSz` or `newPx` must be supplied.
73///
74/// OKX docs: <https://www.okx.com/docs-v5/en/#spread-trading-websocket-trade-api-ws-amend-order>
75#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
76#[serde(rename_all = "camelCase")]
77#[non_exhaustive]
78pub struct AmendSpreadOrderRequest {
79    /// OKX order ID.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub ord_id: Option<String>,
82    /// Client order ID.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub cl_ord_id: Option<String>,
85    /// Client amendment request ID.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub req_id: Option<String>,
88    /// New total order quantity.
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub new_sz: Option<String>,
91    /// New order price.
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub new_px: Option<String>,
94}
95
96impl AmendSpreadOrderRequest {
97    /// Identify the spread order by OKX order ID.
98    pub fn by_order_id(ord_id: impl Into<String>) -> Self {
99        Self {
100            ord_id: Some(ord_id.into()),
101            ..Self::default()
102        }
103    }
104
105    /// Identify the spread order by client order ID.
106    pub fn by_client_order_id(cl_ord_id: impl Into<String>) -> Self {
107        Self {
108            cl_ord_id: Some(cl_ord_id.into()),
109            ..Self::default()
110        }
111    }
112
113    /// Set the client amendment request ID.
114    pub fn request_id(mut self, req_id: impl Into<String>) -> Self {
115        self.req_id = Some(req_id.into());
116        self
117    }
118
119    /// Set the new total order size.
120    pub fn new_size(mut self, size: impl Into<String>) -> Self {
121        self.new_sz = Some(size.into());
122        self
123    }
124
125    /// Set the new order price.
126    pub fn new_price(mut self, price: impl Into<String>) -> Self {
127        self.new_px = Some(price.into());
128        self
129    }
130}
131
132/// Cancel-spread-order request body (`sprd-cancel-order`).
133///
134/// Either `ordId` or `clOrdId` is required; when both are supplied OKX uses
135/// `ordId`.
136///
137/// OKX docs: <https://www.okx.com/docs-v5/en/#spread-trading-websocket-trade-api-ws-cancel-order>
138#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
139#[serde(rename_all = "camelCase")]
140#[non_exhaustive]
141pub struct CancelSpreadOrderRequest {
142    /// OKX order ID.
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub ord_id: Option<String>,
145    /// Client order ID.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub cl_ord_id: Option<String>,
148}
149
150impl CancelSpreadOrderRequest {
151    /// Identify the spread order by OKX order ID.
152    pub fn by_order_id(ord_id: impl Into<String>) -> Self {
153        Self {
154            ord_id: Some(ord_id.into()),
155            cl_ord_id: None,
156        }
157    }
158
159    /// Identify the spread order by client order ID.
160    pub fn by_client_order_id(cl_ord_id: impl Into<String>) -> Self {
161        Self {
162            ord_id: None,
163            cl_ord_id: Some(cl_ord_id.into()),
164        }
165    }
166}
167
168/// Cancel-all-spread-orders request body (`sprd-mass-cancel`).
169///
170/// When `sprdId` is omitted, OKX cancels pending orders across all spreads.
171///
172/// OKX docs: <https://www.okx.com/docs-v5/en/#spread-trading-websocket-trade-api-ws-cancel-all-orders>
173#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
174#[serde(rename_all = "camelCase")]
175#[non_exhaustive]
176pub struct MassCancelSpreadOrdersRequest {
177    /// Optional spread ID filter.
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub sprd_id: Option<String>,
180}
181
182impl MassCancelSpreadOrdersRequest {
183    /// Cancel pending orders across all spreads.
184    pub fn all() -> Self {
185        Self::default()
186    }
187
188    /// Cancel pending orders for one spread only.
189    pub fn for_spread(sprd_id: impl Into<String>) -> Self {
190        Self {
191            sprd_id: Some(sprd_id.into()),
192        }
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn serializes_documented_place_order_example() {
202        let request = PlaceSpreadOrderRequest::new("BTC-USDT_BTC-USDT-SWAP", "buy", "limit", "2")
203            .client_order_id("b15")
204            .price("2.15");
205        let value = serde_json::to_value(request).unwrap();
206        assert_eq!(value["sprdId"], "BTC-USDT_BTC-USDT-SWAP");
207        assert_eq!(value["ordType"], "limit");
208        assert_eq!(value["px"], "2.15");
209    }
210
211    #[test]
212    fn mass_cancel_all_serializes_as_empty_object() {
213        let value = serde_json::to_value(MassCancelSpreadOrdersRequest::all()).unwrap();
214        assert_eq!(value, serde_json::json!({}));
215    }
216}