Skip to main content

rust_okx/api/trade/
requests.rs

1use serde::Serialize;
2
3use crate::model::{
4    OrderSide, OrderState, OrderType, PositionSide, TradeMode, ValidateRequest, one_of,
5    optional_non_empty, optional_unsigned_integer_string, range_u64,
6};
7
8mod advanced;
9mod algo;
10
11pub use advanced::*;
12pub use algo::*;
13
14/// A request to place an order.
15///
16/// Construct with [`PlaceOrderRequest::new`] (required fields) and chain setters
17/// for optional fields. Optional fields are omitted from the request body when
18/// unset.
19#[derive(Debug, Clone, Serialize)]
20pub struct PlaceOrderRequest {
21    #[serde(rename = "instId")]
22    inst_id: String,
23    #[serde(rename = "tdMode")]
24    td_mode: TradeMode,
25    side: OrderSide,
26    #[serde(rename = "ordType")]
27    ord_type: OrderType,
28    sz: String,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    ccy: Option<String>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    tag: Option<String>,
33    #[serde(rename = "px", skip_serializing_if = "Option::is_none")]
34    px: Option<String>,
35    #[serde(rename = "posSide", skip_serializing_if = "Option::is_none")]
36    pos_side: Option<PositionSide>,
37    #[serde(rename = "clOrdId", skip_serializing_if = "Option::is_none")]
38    cl_ord_id: Option<String>,
39    #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
40    reduce_only: Option<bool>,
41    #[serde(rename = "tgtCcy", skip_serializing_if = "Option::is_none")]
42    tgt_ccy: Option<String>,
43}
44
45impl PlaceOrderRequest {
46    /// Create a new order request with the required fields.
47    pub fn new(
48        inst_id: impl Into<String>,
49        td_mode: TradeMode,
50        side: OrderSide,
51        ord_type: OrderType,
52        sz: impl Into<String>,
53    ) -> Self {
54        Self {
55            inst_id: inst_id.into(),
56            td_mode,
57            side,
58            ord_type,
59            sz: sz.into(),
60            ccy: None,
61            tag: None,
62            px: None,
63            pos_side: None,
64            cl_ord_id: None,
65            reduce_only: None,
66            tgt_ccy: None,
67        }
68    }
69
70    /// Set the order price (required for `limit`-style orders).
71    pub fn price(mut self, px: impl Into<String>) -> Self {
72        self.px = Some(px.into());
73        self
74    }
75
76    /// Set the position side (`long`/`short`/`net`).
77    pub fn position_side(mut self, pos_side: PositionSide) -> Self {
78        self.pos_side = Some(pos_side);
79        self
80    }
81
82    /// Set a client-supplied order ID.
83    pub fn client_order_id(mut self, cl_ord_id: impl Into<String>) -> Self {
84        self.cl_ord_id = Some(cl_ord_id.into());
85        self
86    }
87
88    /// Mark the order as reduce-only.
89    pub fn reduce_only(mut self, reduce_only: bool) -> Self {
90        self.reduce_only = Some(reduce_only);
91        self
92    }
93
94    /// Set the quantity unit for spot market orders (`base_ccy`/`quote_ccy`).
95    pub fn target_ccy(mut self, tgt_ccy: impl Into<String>) -> Self {
96        self.tgt_ccy = Some(tgt_ccy.into());
97        self
98    }
99
100    /// Set the margin currency.
101    pub fn currency(mut self, ccy: impl Into<String>) -> Self {
102        self.ccy = Some(ccy.into());
103        self
104    }
105
106    /// Set an order tag.
107    pub fn tag(mut self, tag: impl Into<String>) -> Self {
108        self.tag = Some(tag.into());
109        self
110    }
111}
112
113/// A request to cancel an order.
114#[derive(Debug, Clone, Serialize)]
115pub struct CancelOrderRequest {
116    #[serde(rename = "instId")]
117    inst_id: String,
118    #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
119    ord_id: Option<String>,
120    #[serde(rename = "clOrdId", skip_serializing_if = "Option::is_none")]
121    cl_ord_id: Option<String>,
122}
123
124impl CancelOrderRequest {
125    /// Cancel by OKX order ID.
126    pub fn by_order_id(inst_id: impl Into<String>, ord_id: impl Into<String>) -> Self {
127        Self {
128            inst_id: inst_id.into(),
129            ord_id: Some(ord_id.into()),
130            cl_ord_id: None,
131        }
132    }
133
134    /// Cancel by client order ID.
135    pub fn by_client_order_id(inst_id: impl Into<String>, cl_ord_id: impl Into<String>) -> Self {
136        Self {
137            inst_id: inst_id.into(),
138            ord_id: None,
139            cl_ord_id: Some(cl_ord_id.into()),
140        }
141    }
142}
143
144/// A request to amend an order.
145#[derive(Debug, Clone, Serialize)]
146pub struct AmendOrderRequest {
147    #[serde(rename = "instId")]
148    inst_id: String,
149    #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
150    ord_id: Option<String>,
151    #[serde(rename = "clOrdId", skip_serializing_if = "Option::is_none")]
152    cl_ord_id: Option<String>,
153    #[serde(rename = "reqId", skip_serializing_if = "Option::is_none")]
154    req_id: Option<String>,
155    #[serde(rename = "cxlOnFail", skip_serializing_if = "Option::is_none")]
156    cxl_on_fail: Option<bool>,
157    #[serde(rename = "newSz", skip_serializing_if = "Option::is_none")]
158    new_sz: Option<String>,
159    #[serde(rename = "newPx", skip_serializing_if = "Option::is_none")]
160    new_px: Option<String>,
161}
162
163impl AmendOrderRequest {
164    /// Create an amend-order request for an instrument.
165    pub fn new(inst_id: impl Into<String>) -> Self {
166        Self {
167            inst_id: inst_id.into(),
168            ord_id: None,
169            cl_ord_id: None,
170            req_id: None,
171            cxl_on_fail: None,
172            new_sz: None,
173            new_px: None,
174        }
175    }
176
177    /// Set the OKX order ID.
178    pub fn order_id(mut self, ord_id: impl Into<String>) -> Self {
179        self.ord_id = Some(ord_id.into());
180        self
181    }
182
183    /// Set the client order ID.
184    pub fn client_order_id(mut self, cl_ord_id: impl Into<String>) -> Self {
185        self.cl_ord_id = Some(cl_ord_id.into());
186        self
187    }
188
189    /// Set a request ID.
190    pub fn request_id(mut self, req_id: impl Into<String>) -> Self {
191        self.req_id = Some(req_id.into());
192        self
193    }
194
195    /// Set whether OKX should cancel the order if amendment fails.
196    pub fn cancel_on_fail(mut self, cxl_on_fail: bool) -> Self {
197        self.cxl_on_fail = Some(cxl_on_fail);
198        self
199    }
200
201    /// Set the new order size.
202    pub fn new_size(mut self, new_sz: impl Into<String>) -> Self {
203        self.new_sz = Some(new_sz.into());
204        self
205    }
206
207    /// Set the new order price.
208    pub fn new_price(mut self, new_px: impl Into<String>) -> Self {
209        self.new_px = Some(new_px.into());
210        self
211    }
212}
213
214/// A request to close positions.
215#[derive(Debug, Clone, Serialize)]
216pub struct ClosePositionRequest {
217    #[serde(rename = "instId")]
218    inst_id: String,
219    #[serde(rename = "mgnMode")]
220    mgn_mode: TradeMode,
221    #[serde(rename = "posSide", skip_serializing_if = "Option::is_none")]
222    pos_side: Option<PositionSide>,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    ccy: Option<String>,
225    #[serde(rename = "autoCxl", skip_serializing_if = "Option::is_none")]
226    auto_cancel: Option<bool>,
227    #[serde(rename = "clOrdId", skip_serializing_if = "Option::is_none")]
228    cl_ord_id: Option<String>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    tag: Option<String>,
231}
232
233impl ClosePositionRequest {
234    /// Create a close-position request.
235    pub fn new(inst_id: impl Into<String>, mgn_mode: TradeMode) -> Self {
236        Self {
237            inst_id: inst_id.into(),
238            mgn_mode,
239            pos_side: None,
240            ccy: None,
241            auto_cancel: None,
242            cl_ord_id: None,
243            tag: None,
244        }
245    }
246
247    /// Set the position side.
248    pub fn position_side(mut self, pos_side: PositionSide) -> Self {
249        self.pos_side = Some(pos_side);
250        self
251    }
252
253    /// Set the margin currency.
254    pub fn currency(mut self, ccy: impl Into<String>) -> Self {
255        self.ccy = Some(ccy.into());
256        self
257    }
258
259    /// Set whether pending orders should be canceled automatically.
260    pub fn auto_cancel(mut self, auto_cancel: bool) -> Self {
261        self.auto_cancel = Some(auto_cancel);
262        self
263    }
264
265    /// Set the client order ID.
266    pub fn client_order_id(mut self, cl_ord_id: impl Into<String>) -> Self {
267        self.cl_ord_id = Some(cl_ord_id.into());
268        self
269    }
270
271    /// Set an order tag.
272    pub fn tag(mut self, tag: impl Into<String>) -> Self {
273        self.tag = Some(tag.into());
274        self
275    }
276}
277
278/// Query parameters for pending order lists.
279#[derive(Debug, Clone, Default, Serialize)]
280pub struct OrderListRequest {
281    #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
282    inst_type: Option<crate::model::InstType>,
283    #[serde(rename = "uly", skip_serializing_if = "Option::is_none")]
284    underlying: Option<String>,
285    #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
286    inst_id: Option<String>,
287    #[serde(rename = "ordType", skip_serializing_if = "Option::is_none")]
288    ord_type: Option<OrderType>,
289    #[serde(skip_serializing_if = "Option::is_none")]
290    state: Option<OrderState>,
291    #[serde(skip_serializing_if = "Option::is_none")]
292    after: Option<String>,
293    #[serde(skip_serializing_if = "Option::is_none")]
294    before: Option<String>,
295    #[serde(skip_serializing_if = "Option::is_none")]
296    limit: Option<u32>,
297    #[serde(rename = "instFamily", skip_serializing_if = "Option::is_none")]
298    inst_family: Option<String>,
299}
300
301impl OrderListRequest {
302    /// Create an empty order-list query.
303    pub fn new() -> Self {
304        Self::default()
305    }
306
307    /// Set the instrument type filter.
308    pub fn inst_type(mut self, inst_type: crate::model::InstType) -> Self {
309        self.inst_type = Some(inst_type);
310        self
311    }
312
313    /// Set the underlying filter.
314    pub fn underlying(mut self, underlying: impl Into<String>) -> Self {
315        self.underlying = Some(underlying.into());
316        self
317    }
318
319    /// Set the instrument ID filter.
320    pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
321        self.inst_id = Some(inst_id.into());
322        self
323    }
324
325    /// Set the order type filter.
326    pub fn order_type(mut self, ord_type: OrderType) -> Self {
327        self.ord_type = Some(ord_type);
328        self
329    }
330
331    /// Set the order state filter.
332    pub fn state(mut self, state: OrderState) -> Self {
333        self.state = Some(state);
334        self
335    }
336
337    /// Return records after this pagination cursor.
338    pub fn after(mut self, after: impl Into<String>) -> Self {
339        self.after = Some(after.into());
340        self
341    }
342
343    /// Return records before this pagination cursor.
344    pub fn before(mut self, before: impl Into<String>) -> Self {
345        self.before = Some(before.into());
346        self
347    }
348
349    /// Set the maximum number of rows to return.
350    pub fn limit(mut self, limit: u32) -> Self {
351        self.limit = Some(limit);
352        self
353    }
354
355    /// Set the instrument family filter.
356    pub fn inst_family(mut self, inst_family: impl Into<String>) -> Self {
357        self.inst_family = Some(inst_family.into());
358        self
359    }
360}
361
362/// Query parameters for order history.
363#[derive(Debug, Clone, Serialize)]
364pub struct OrderHistoryRequest {
365    #[serde(flatten)]
366    base: OrderListRequest,
367    #[serde(skip_serializing_if = "Option::is_none")]
368    begin: Option<String>,
369    #[serde(skip_serializing_if = "Option::is_none")]
370    end: Option<String>,
371}
372
373impl OrderHistoryRequest {
374    /// Create an order-history query with the required instrument type.
375    pub fn new(inst_type: crate::model::InstType) -> Self {
376        Self {
377            base: OrderListRequest::new().inst_type(inst_type),
378            begin: None,
379            end: None,
380        }
381    }
382
383    /// Set the common order-list filters.
384    pub fn filters(mut self, base: OrderListRequest) -> Self {
385        self.base = base;
386        self
387    }
388
389    /// Set the begin timestamp.
390    pub fn begin(mut self, begin: impl Into<String>) -> Self {
391        self.begin = Some(begin.into());
392        self
393    }
394
395    /// Set the end timestamp.
396    pub fn end(mut self, end: impl Into<String>) -> Self {
397        self.end = Some(end.into());
398        self
399    }
400}
401
402/// Query parameters for fills.
403#[derive(Debug, Clone, Default, Serialize)]
404pub struct FillsRequest {
405    #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
406    inst_type: Option<crate::model::InstType>,
407    #[serde(rename = "uly", skip_serializing_if = "Option::is_none")]
408    underlying: Option<String>,
409    #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
410    inst_id: Option<String>,
411    #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
412    ord_id: Option<String>,
413    #[serde(skip_serializing_if = "Option::is_none")]
414    after: Option<String>,
415    #[serde(skip_serializing_if = "Option::is_none")]
416    before: Option<String>,
417    #[serde(skip_serializing_if = "Option::is_none")]
418    begin: Option<String>,
419    #[serde(skip_serializing_if = "Option::is_none")]
420    end: Option<String>,
421    #[serde(skip_serializing_if = "Option::is_none")]
422    limit: Option<u32>,
423    #[serde(rename = "instFamily", skip_serializing_if = "Option::is_none")]
424    inst_family: Option<String>,
425}
426
427impl FillsRequest {
428    /// Create an empty fills query.
429    pub fn new() -> Self {
430        Self::default()
431    }
432
433    /// Set the instrument type filter.
434    pub fn inst_type(mut self, inst_type: crate::model::InstType) -> Self {
435        self.inst_type = Some(inst_type);
436        self
437    }
438
439    /// Set the underlying filter.
440    pub fn underlying(mut self, underlying: impl Into<String>) -> Self {
441        self.underlying = Some(underlying.into());
442        self
443    }
444
445    /// Set the instrument ID filter.
446    pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
447        self.inst_id = Some(inst_id.into());
448        self
449    }
450
451    /// Set the order ID filter.
452    pub fn order_id(mut self, ord_id: impl Into<String>) -> Self {
453        self.ord_id = Some(ord_id.into());
454        self
455    }
456
457    /// Return records after this pagination cursor.
458    pub fn after(mut self, after: impl Into<String>) -> Self {
459        self.after = Some(after.into());
460        self
461    }
462
463    /// Return records before this pagination cursor.
464    pub fn before(mut self, before: impl Into<String>) -> Self {
465        self.before = Some(before.into());
466        self
467    }
468
469    /// Set the begin timestamp.
470    pub fn begin(mut self, begin: impl Into<String>) -> Self {
471        self.begin = Some(begin.into());
472        self
473    }
474
475    /// Set the end timestamp.
476    pub fn end(mut self, end: impl Into<String>) -> Self {
477        self.end = Some(end.into());
478        self
479    }
480
481    /// Set the maximum number of rows to return.
482    pub fn limit(mut self, limit: u32) -> Self {
483        self.limit = Some(limit);
484        self
485    }
486
487    /// Set the instrument family filter.
488    pub fn inst_family(mut self, inst_family: impl Into<String>) -> Self {
489        self.inst_family = Some(inst_family.into());
490        self
491    }
492}
493
494/// Query parameters for historical fills.
495///
496/// This is intentionally separate from [`FillsRequest`] because OKX documents
497/// `instType` as required for `GET /api/v5/trade/fills-history`, while it is
498/// optional for `GET /api/v5/trade/fills`.
499#[derive(Debug, Clone, Serialize)]
500pub struct FillHistoryRequest {
501    #[serde(rename = "instType")]
502    inst_type: crate::model::InstType,
503    #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
504    inst_id: Option<String>,
505    #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
506    ord_id: Option<String>,
507    #[serde(skip_serializing_if = "Option::is_none")]
508    after: Option<String>,
509    #[serde(skip_serializing_if = "Option::is_none")]
510    before: Option<String>,
511    #[serde(skip_serializing_if = "Option::is_none")]
512    begin: Option<String>,
513    #[serde(skip_serializing_if = "Option::is_none")]
514    end: Option<String>,
515    #[serde(skip_serializing_if = "Option::is_none")]
516    limit: Option<u32>,
517}
518
519impl FillHistoryRequest {
520    /// Create a historical fills query with the required instrument type.
521    pub fn new(inst_type: crate::model::InstType) -> Self {
522        Self {
523            inst_type,
524            inst_id: None,
525            ord_id: None,
526            after: None,
527            before: None,
528            begin: None,
529            end: None,
530            limit: None,
531        }
532    }
533
534    /// Set the instrument ID filter.
535    pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
536        self.inst_id = Some(inst_id.into());
537        self
538    }
539
540    /// Set the order ID filter.
541    pub fn order_id(mut self, ord_id: impl Into<String>) -> Self {
542        self.ord_id = Some(ord_id.into());
543        self
544    }
545
546    /// Return records after this pagination cursor.
547    pub fn after(mut self, after: impl Into<String>) -> Self {
548        self.after = Some(after.into());
549        self
550    }
551
552    /// Return records before this pagination cursor.
553    pub fn before(mut self, before: impl Into<String>) -> Self {
554        self.before = Some(before.into());
555        self
556    }
557
558    /// Set the begin timestamp.
559    pub fn begin(mut self, begin: impl Into<String>) -> Self {
560        self.begin = Some(begin.into());
561        self
562    }
563
564    /// Set the end timestamp.
565    pub fn end(mut self, end: impl Into<String>) -> Self {
566        self.end = Some(end.into());
567        self
568    }
569
570    /// Set the maximum number of rows to return.
571    pub fn limit(mut self, limit: u32) -> Self {
572        self.limit = Some(limit);
573        self
574    }
575}
576
577impl ValidateRequest for FillHistoryRequest {
578    fn validate(&self) -> Result<(), crate::model::RequestValidationError> {
579        one_of("instType", self.inst_type.as_str(), &["SPOT"], "SPOT")?;
580        optional_non_empty("instId", self.inst_id.as_deref())?;
581        optional_unsigned_integer_string("ordId", self.ord_id.as_deref())?;
582        optional_unsigned_integer_string("after", self.after.as_deref())?;
583        optional_unsigned_integer_string("before", self.before.as_deref())?;
584        optional_unsigned_integer_string("begin", self.begin.as_deref())?;
585        optional_unsigned_integer_string("end", self.end.as_deref())?;
586        if let Some(limit) = self.limit {
587            range_u64("limit", u64::from(limit), 1, 100)?;
588        }
589        Ok(())
590    }
591}