Skip to main content

rust_okx/api/trade/requests/
algo.rs

1use serde::Serialize;
2
3/// One attached take-profit/stop-loss definition for an algo order.
4#[derive(Debug, Clone, Default, Serialize)]
5pub struct AttachedAlgoOrderRequest {
6    #[serde(rename = "attachAlgoClOrdId", skip_serializing_if = "Option::is_none")]
7    attach_algo_cl_ord_id: Option<String>,
8    #[serde(rename = "tpTriggerPx", skip_serializing_if = "Option::is_none")]
9    tp_trigger_px: Option<String>,
10    #[serde(rename = "tpTriggerRatio", skip_serializing_if = "Option::is_none")]
11    tp_trigger_ratio: Option<String>,
12    #[serde(rename = "tpTriggerPxType", skip_serializing_if = "Option::is_none")]
13    tp_trigger_px_type: Option<String>,
14    #[serde(rename = "tpOrdPx", skip_serializing_if = "Option::is_none")]
15    tp_ord_px: Option<String>,
16    #[serde(rename = "slTriggerPx", skip_serializing_if = "Option::is_none")]
17    sl_trigger_px: Option<String>,
18    #[serde(rename = "slTriggerRatio", skip_serializing_if = "Option::is_none")]
19    sl_trigger_ratio: Option<String>,
20    #[serde(rename = "slTriggerPxType", skip_serializing_if = "Option::is_none")]
21    sl_trigger_px_type: Option<String>,
22    #[serde(rename = "slOrdPx", skip_serializing_if = "Option::is_none")]
23    sl_ord_px: Option<String>,
24}
25
26impl AttachedAlgoOrderRequest {
27    /// Create an empty attached TP/SL definition.
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    /// Set the client-supplied attached algo ID.
33    pub fn client_algo_order_id(mut self, value: impl Into<String>) -> Self {
34        self.attach_algo_cl_ord_id = Some(value.into());
35        self
36    }
37
38    /// Configure take profit using a trigger price.
39    pub fn take_profit(
40        mut self,
41        trigger_px: impl Into<String>,
42        order_px: impl Into<String>,
43    ) -> Self {
44        self.tp_trigger_px = Some(trigger_px.into());
45        self.tp_ord_px = Some(order_px.into());
46        self
47    }
48
49    /// Configure take profit using a trigger ratio.
50    pub fn take_profit_ratio(
51        mut self,
52        trigger_ratio: impl Into<String>,
53        order_px: impl Into<String>,
54    ) -> Self {
55        self.tp_trigger_ratio = Some(trigger_ratio.into());
56        self.tp_ord_px = Some(order_px.into());
57        self
58    }
59
60    /// Set the take-profit trigger price source.
61    pub fn take_profit_price_type(mut self, value: impl Into<String>) -> Self {
62        self.tp_trigger_px_type = Some(value.into());
63        self
64    }
65
66    /// Configure stop loss using a trigger price.
67    pub fn stop_loss(mut self, trigger_px: impl Into<String>, order_px: impl Into<String>) -> Self {
68        self.sl_trigger_px = Some(trigger_px.into());
69        self.sl_ord_px = Some(order_px.into());
70        self
71    }
72
73    /// Configure stop loss using a trigger ratio.
74    pub fn stop_loss_ratio(
75        mut self,
76        trigger_ratio: impl Into<String>,
77        order_px: impl Into<String>,
78    ) -> Self {
79        self.sl_trigger_ratio = Some(trigger_ratio.into());
80        self.sl_ord_px = Some(order_px.into());
81        self
82    }
83
84    /// Set the stop-loss trigger price source.
85    pub fn stop_loss_price_type(mut self, value: impl Into<String>) -> Self {
86        self.sl_trigger_px_type = Some(value.into());
87        self
88    }
89}
90
91/// One trigger definition used by a `smart_iceberg` request.
92#[derive(Debug, Clone, Serialize)]
93pub struct SmartIcebergTriggerRequest {
94    #[serde(rename = "triggerAction")]
95    trigger_action: String,
96    #[serde(rename = "triggerStrategy")]
97    trigger_strategy: String,
98    #[serde(rename = "triggerPx", skip_serializing_if = "Option::is_none")]
99    trigger_px: Option<String>,
100    #[serde(rename = "triggerCond", skip_serializing_if = "Option::is_none")]
101    trigger_cond: Option<String>,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    timeframe: Option<String>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    thold: Option<String>,
106    #[serde(rename = "timePeriod", skip_serializing_if = "Option::is_none")]
107    time_period: Option<String>,
108}
109
110impl SmartIcebergTriggerRequest {
111    /// Create a start trigger with strategy `instant`, `price`, or `rsi`.
112    pub fn new(trigger_strategy: impl Into<String>) -> Self {
113        Self {
114            trigger_action: "start".to_owned(),
115            trigger_strategy: trigger_strategy.into(),
116            trigger_px: None,
117            trigger_cond: None,
118            timeframe: None,
119            thold: None,
120            time_period: None,
121        }
122    }
123
124    /// Set a price trigger and optional crossing condition.
125    pub fn price(mut self, trigger_px: impl Into<String>) -> Self {
126        self.trigger_px = Some(trigger_px.into());
127        self
128    }
129
130    /// Set the trigger condition.
131    pub fn condition(mut self, value: impl Into<String>) -> Self {
132        self.trigger_cond = Some(value.into());
133        self
134    }
135
136    /// Set the RSI candle timeframe.
137    pub fn timeframe(mut self, value: impl Into<String>) -> Self {
138        self.timeframe = Some(value.into());
139        self
140    }
141
142    /// Set the RSI threshold from 1 through 100.
143    pub fn threshold(mut self, value: impl Into<String>) -> Self {
144        self.thold = Some(value.into());
145        self
146    }
147
148    /// Set the RSI period. OKX currently fixes this value at `14`.
149    pub fn time_period(mut self, value: impl Into<String>) -> Self {
150        self.time_period = Some(value.into());
151        self
152    }
153}
154
155/// Request body for `POST /api/v5/trade/order-algo`.
156#[derive(Debug, Clone, Serialize)]
157pub struct AlgoOrderRequest {
158    #[serde(rename = "instId")]
159    inst_id: String,
160    #[serde(rename = "tdMode")]
161    td_mode: String,
162    side: String,
163    #[serde(rename = "ordType")]
164    ord_type: String,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    sz: Option<String>,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    ccy: Option<String>,
169    #[serde(rename = "posSide", skip_serializing_if = "Option::is_none")]
170    pos_side: Option<String>,
171    #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
172    reduce_only: Option<bool>,
173    #[serde(rename = "algoClOrdId", skip_serializing_if = "Option::is_none")]
174    algo_cl_ord_id: Option<String>,
175    #[serde(rename = "tgtCcy", skip_serializing_if = "Option::is_none")]
176    tgt_ccy: Option<String>,
177    #[serde(rename = "closeFraction", skip_serializing_if = "Option::is_none")]
178    close_fraction: Option<String>,
179    #[serde(rename = "tradeQuoteCcy", skip_serializing_if = "Option::is_none")]
180    trade_quote_ccy: Option<String>,
181    #[serde(rename = "triggerPx", skip_serializing_if = "Option::is_none")]
182    trigger_px: Option<String>,
183    #[serde(rename = "orderPx", skip_serializing_if = "Option::is_none")]
184    order_px: Option<String>,
185    #[serde(rename = "advanceOrdType", skip_serializing_if = "Option::is_none")]
186    advance_ord_type: Option<String>,
187    #[serde(rename = "triggerPxType", skip_serializing_if = "Option::is_none")]
188    trigger_px_type: Option<String>,
189    #[serde(rename = "tpTriggerPx", skip_serializing_if = "Option::is_none")]
190    tp_trigger_px: Option<String>,
191    #[serde(rename = "tpTriggerPxType", skip_serializing_if = "Option::is_none")]
192    tp_trigger_px_type: Option<String>,
193    #[serde(rename = "tpOrdPx", skip_serializing_if = "Option::is_none")]
194    tp_ord_px: Option<String>,
195    #[serde(rename = "tpOrdKind", skip_serializing_if = "Option::is_none")]
196    tp_ord_kind: Option<String>,
197    #[serde(rename = "slTriggerPx", skip_serializing_if = "Option::is_none")]
198    sl_trigger_px: Option<String>,
199    #[serde(rename = "slTriggerPxType", skip_serializing_if = "Option::is_none")]
200    sl_trigger_px_type: Option<String>,
201    #[serde(rename = "slOrdPx", skip_serializing_if = "Option::is_none")]
202    sl_ord_px: Option<String>,
203    #[serde(rename = "cxlOnClosePos", skip_serializing_if = "Option::is_none")]
204    cxl_on_close_pos: Option<bool>,
205    #[serde(rename = "attachAlgoOrds", skip_serializing_if = "Option::is_none")]
206    attach_algo_ords: Option<Vec<AttachedAlgoOrderRequest>>,
207    #[serde(rename = "callbackRatio", skip_serializing_if = "Option::is_none")]
208    callback_ratio: Option<String>,
209    #[serde(rename = "callbackSpread", skip_serializing_if = "Option::is_none")]
210    callback_spread: Option<String>,
211    #[serde(rename = "activePx", skip_serializing_if = "Option::is_none")]
212    active_px: Option<String>,
213    #[serde(rename = "chaseType", skip_serializing_if = "Option::is_none")]
214    chase_type: Option<String>,
215    #[serde(rename = "chaseVal", skip_serializing_if = "Option::is_none")]
216    chase_val: Option<String>,
217    #[serde(rename = "maxChaseType", skip_serializing_if = "Option::is_none")]
218    max_chase_type: Option<String>,
219    #[serde(rename = "maxChaseVal", skip_serializing_if = "Option::is_none")]
220    max_chase_val: Option<String>,
221    #[serde(rename = "pxVar", skip_serializing_if = "Option::is_none")]
222    px_var: Option<String>,
223    #[serde(rename = "pxSpread", skip_serializing_if = "Option::is_none")]
224    px_spread: Option<String>,
225    #[serde(rename = "szLimit", skip_serializing_if = "Option::is_none")]
226    sz_limit: Option<String>,
227    #[serde(rename = "pxLimit", skip_serializing_if = "Option::is_none")]
228    px_limit: Option<String>,
229    #[serde(rename = "timeInterval", skip_serializing_if = "Option::is_none")]
230    time_interval: Option<String>,
231    #[serde(rename = "lmtOrderNumber", skip_serializing_if = "Option::is_none")]
232    lmt_order_number: Option<String>,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    aggressiveness: Option<String>,
235    #[serde(rename = "triggerParams", skip_serializing_if = "Option::is_none")]
236    trigger_params: Option<Vec<SmartIcebergTriggerRequest>>,
237    #[serde(skip_serializing_if = "Option::is_none")]
238    tag: Option<String>,
239}
240
241impl AlgoOrderRequest {
242    /// Create an algo-order request with a quantity.
243    pub fn new(
244        inst_id: impl Into<String>,
245        td_mode: impl Into<String>,
246        side: impl Into<String>,
247        ord_type: impl Into<String>,
248        sz: impl Into<String>,
249    ) -> Self {
250        Self {
251            inst_id: inst_id.into(),
252            td_mode: td_mode.into(),
253            side: side.into(),
254            ord_type: ord_type.into(),
255            sz: Some(sz.into()),
256            ccy: None,
257            pos_side: None,
258            reduce_only: None,
259            algo_cl_ord_id: None,
260            tgt_ccy: None,
261            close_fraction: None,
262            trade_quote_ccy: None,
263            trigger_px: None,
264            order_px: None,
265            advance_ord_type: None,
266            trigger_px_type: None,
267            tp_trigger_px: None,
268            tp_trigger_px_type: None,
269            tp_ord_px: None,
270            tp_ord_kind: None,
271            sl_trigger_px: None,
272            sl_trigger_px_type: None,
273            sl_ord_px: None,
274            cxl_on_close_pos: None,
275            attach_algo_ords: None,
276            callback_ratio: None,
277            callback_spread: None,
278            active_px: None,
279            chase_type: None,
280            chase_val: None,
281            max_chase_type: None,
282            max_chase_val: None,
283            px_var: None,
284            px_spread: None,
285            sz_limit: None,
286            px_limit: None,
287            time_interval: None,
288            lmt_order_number: None,
289            aggressiveness: None,
290            trigger_params: None,
291            tag: None,
292        }
293    }
294
295    /// Replace `sz` with the only currently supported full-close fraction, `1`.
296    pub fn full_close(mut self) -> Self {
297        self.sz = None;
298        self.close_fraction = Some("1".to_owned());
299        self
300    }
301
302    /// Set the margin currency.
303    pub fn currency(mut self, value: impl Into<String>) -> Self {
304        self.ccy = Some(value.into());
305        self
306    }
307
308    /// Set the position side (`long`, `short`, or `net`).
309    pub fn position_side(mut self, value: impl Into<String>) -> Self {
310        self.pos_side = Some(value.into());
311        self
312    }
313
314    /// Set the reduce-only flag.
315    pub fn reduce_only(mut self, value: bool) -> Self {
316        self.reduce_only = Some(value);
317        self
318    }
319
320    /// Set the client-supplied algo ID.
321    pub fn client_algo_order_id(mut self, value: impl Into<String>) -> Self {
322        self.algo_cl_ord_id = Some(value.into());
323        self
324    }
325
326    /// Set the Spot quantity unit (`base_ccy` or `quote_ccy`).
327    pub fn target_currency(mut self, value: impl Into<String>) -> Self {
328        self.tgt_ccy = Some(value.into());
329        self
330    }
331
332    /// Set the quote currency used for Spot trading.
333    pub fn trade_quote_currency(mut self, value: impl Into<String>) -> Self {
334        self.trade_quote_ccy = Some(value.into());
335        self
336    }
337
338    /// Configure a trigger order's trigger and execution prices.
339    pub fn trigger(mut self, px: impl Into<String>, order_px: impl Into<String>) -> Self {
340        self.trigger_px = Some(px.into());
341        self.order_px = Some(order_px.into());
342        self
343    }
344
345    /// Set trigger execution type (`fok` or `ioc`).
346    pub fn advance_order_type(mut self, value: impl Into<String>) -> Self {
347        self.advance_ord_type = Some(value.into());
348        self
349    }
350
351    /// Set trigger price type (`last`, `index`, or `mark`).
352    pub fn trigger_price_type(mut self, value: impl Into<String>) -> Self {
353        self.trigger_px_type = Some(value.into());
354        self
355    }
356
357    /// Configure take-profit trigger and order prices.
358    pub fn take_profit(
359        mut self,
360        trigger_px: impl Into<String>,
361        order_px: impl Into<String>,
362    ) -> Self {
363        self.tp_trigger_px = Some(trigger_px.into());
364        self.tp_ord_px = Some(order_px.into());
365        self
366    }
367
368    /// Configure a limit take-profit order that does not require a trigger price.
369    pub fn limit_take_profit(mut self, order_px: impl Into<String>) -> Self {
370        self.tp_ord_kind = Some("limit".to_owned());
371        self.tp_ord_px = Some(order_px.into());
372        self
373    }
374
375    /// Set the take-profit trigger source.
376    pub fn take_profit_price_type(mut self, value: impl Into<String>) -> Self {
377        self.tp_trigger_px_type = Some(value.into());
378        self
379    }
380
381    /// Configure stop-loss trigger and order prices.
382    pub fn stop_loss(mut self, trigger_px: impl Into<String>, order_px: impl Into<String>) -> Self {
383        self.sl_trigger_px = Some(trigger_px.into());
384        self.sl_ord_px = Some(order_px.into());
385        self
386    }
387
388    /// Set the stop-loss trigger source.
389    pub fn stop_loss_price_type(mut self, value: impl Into<String>) -> Self {
390        self.sl_trigger_px_type = Some(value.into());
391        self
392    }
393
394    /// Associate TP/SL cancellation with a fully closed position.
395    pub fn cancel_on_close_position(mut self, value: bool) -> Self {
396        self.cxl_on_close_pos = Some(value);
397        self
398    }
399
400    /// Attach TP/SL definitions to the triggered order.
401    pub fn attached_algo_orders(mut self, values: Vec<AttachedAlgoOrderRequest>) -> Self {
402        self.attach_algo_ords = Some(values);
403        self
404    }
405
406    /// Set a trailing-order callback ratio.
407    pub fn callback_ratio(mut self, value: impl Into<String>) -> Self {
408        self.callback_ratio = Some(value.into());
409        self
410    }
411
412    /// Set a trailing-order callback spread.
413    pub fn callback_spread(mut self, value: impl Into<String>) -> Self {
414        self.callback_spread = Some(value.into());
415        self
416    }
417
418    /// Set a trailing-order activation price.
419    pub fn active_price(mut self, value: impl Into<String>) -> Self {
420        self.active_px = Some(value.into());
421        self
422    }
423
424    /// Configure chase type and value.
425    pub fn chase(mut self, chase_type: impl Into<String>, chase_val: impl Into<String>) -> Self {
426        self.chase_type = Some(chase_type.into());
427        self.chase_val = Some(chase_val.into());
428        self
429    }
430
431    /// Configure the optional maximum chase type and value.
432    pub fn maximum_chase(
433        mut self,
434        chase_type: impl Into<String>,
435        chase_val: impl Into<String>,
436    ) -> Self {
437        self.max_chase_type = Some(chase_type.into());
438        self.max_chase_val = Some(chase_val.into());
439        self
440    }
441
442    /// Configure TWAP strategy fields using a percentage variance.
443    pub fn twap_by_variance(
444        mut self,
445        px_var: impl Into<String>,
446        sz_limit: impl Into<String>,
447        px_limit: impl Into<String>,
448        time_interval: impl Into<String>,
449    ) -> Self {
450        self.px_var = Some(px_var.into());
451        self.px_spread = None;
452        self.sz_limit = Some(sz_limit.into());
453        self.px_limit = Some(px_limit.into());
454        self.time_interval = Some(time_interval.into());
455        self
456    }
457
458    /// Configure TWAP strategy fields using an absolute price spread.
459    pub fn twap_by_spread(
460        mut self,
461        px_spread: impl Into<String>,
462        sz_limit: impl Into<String>,
463        px_limit: impl Into<String>,
464        time_interval: impl Into<String>,
465    ) -> Self {
466        self.px_var = None;
467        self.px_spread = Some(px_spread.into());
468        self.sz_limit = Some(sz_limit.into());
469        self.px_limit = Some(px_limit.into());
470        self.time_interval = Some(time_interval.into());
471        self
472    }
473
474    /// Configure required Smart Iceberg execution fields.
475    pub fn smart_iceberg(
476        mut self,
477        sz_limit: impl Into<String>,
478        lmt_order_number: impl Into<String>,
479        aggressiveness: impl Into<String>,
480    ) -> Self {
481        self.sz_limit = Some(sz_limit.into());
482        self.lmt_order_number = Some(lmt_order_number.into());
483        self.aggressiveness = Some(aggressiveness.into());
484        self
485    }
486
487    /// Set an optional Smart Iceberg price limit.
488    pub fn price_limit(mut self, value: impl Into<String>) -> Self {
489        self.px_limit = Some(value.into());
490        self
491    }
492
493    /// Set Smart Iceberg trigger parameters.
494    pub fn smart_iceberg_triggers(mut self, values: Vec<SmartIcebergTriggerRequest>) -> Self {
495        self.trigger_params = Some(values);
496        self
497    }
498
499    /// Set an order tag of at most 16 ASCII alphanumeric characters.
500    pub fn tag(mut self, value: impl Into<String>) -> Self {
501        self.tag = Some(value.into());
502        self
503    }
504}
505
506/// Request body for one entry in `POST /api/v5/trade/cancel-algos`.
507#[derive(Debug, Clone, Serialize)]
508pub struct CancelAlgoOrderRequest {
509    #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
510    algo_id: Option<String>,
511    #[serde(rename = "algoClOrdId", skip_serializing_if = "Option::is_none")]
512    algo_cl_ord_id: Option<String>,
513    #[serde(rename = "instId")]
514    inst_id: String,
515}
516
517impl CancelAlgoOrderRequest {
518    /// Create a cancellation using an OKX algo ID.
519    pub fn new(algo_id: impl Into<String>, inst_id: impl Into<String>) -> Self {
520        Self {
521            algo_id: Some(algo_id.into()),
522            algo_cl_ord_id: None,
523            inst_id: inst_id.into(),
524        }
525    }
526
527    /// Create a cancellation using a client-supplied algo ID.
528    pub fn by_client_algo_order_id(
529        algo_cl_ord_id: impl Into<String>,
530        inst_id: impl Into<String>,
531    ) -> Self {
532        Self {
533            algo_id: None,
534            algo_cl_ord_id: Some(algo_cl_ord_id.into()),
535            inst_id: inst_id.into(),
536        }
537    }
538}
539
540/// Request body for `POST /api/v5/trade/amend-algos`.
541#[derive(Debug, Clone, Serialize)]
542pub struct AmendAlgoOrderRequest {
543    #[serde(rename = "instId")]
544    inst_id: String,
545    #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
546    algo_id: Option<String>,
547    #[serde(rename = "algoClOrdId", skip_serializing_if = "Option::is_none")]
548    algo_cl_ord_id: Option<String>,
549    #[serde(rename = "reqId", skip_serializing_if = "Option::is_none")]
550    req_id: Option<String>,
551    #[serde(rename = "newSz", skip_serializing_if = "Option::is_none")]
552    new_sz: Option<String>,
553    #[serde(rename = "newTpTriggerPx", skip_serializing_if = "Option::is_none")]
554    new_tp_trigger_px: Option<String>,
555    #[serde(rename = "newTpOrdPx", skip_serializing_if = "Option::is_none")]
556    new_tp_ord_px: Option<String>,
557    #[serde(rename = "newTpTriggerPxType", skip_serializing_if = "Option::is_none")]
558    new_tp_trigger_px_type: Option<String>,
559    #[serde(rename = "newSlTriggerPx", skip_serializing_if = "Option::is_none")]
560    new_sl_trigger_px: Option<String>,
561    #[serde(rename = "newSlOrdPx", skip_serializing_if = "Option::is_none")]
562    new_sl_ord_px: Option<String>,
563    #[serde(rename = "newSlTriggerPxType", skip_serializing_if = "Option::is_none")]
564    new_sl_trigger_px_type: Option<String>,
565    #[serde(rename = "cxlOnFail", skip_serializing_if = "Option::is_none")]
566    cancel_on_fail: Option<bool>,
567}
568
569impl AmendAlgoOrderRequest {
570    /// Create an amendment for an instrument; add an algo identifier next.
571    pub fn new(inst_id: impl Into<String>) -> Self {
572        Self {
573            inst_id: inst_id.into(),
574            algo_id: None,
575            algo_cl_ord_id: None,
576            req_id: None,
577            new_sz: None,
578            new_tp_trigger_px: None,
579            new_tp_ord_px: None,
580            new_tp_trigger_px_type: None,
581            new_sl_trigger_px: None,
582            new_sl_ord_px: None,
583            new_sl_trigger_px_type: None,
584            cancel_on_fail: None,
585        }
586    }
587
588    /// Identify the order by OKX algo ID.
589    pub fn algo_id(mut self, value: impl Into<String>) -> Self {
590        self.algo_id = Some(value.into());
591        self
592    }
593
594    /// Identify the order by client-supplied algo ID.
595    pub fn client_algo_order_id(mut self, value: impl Into<String>) -> Self {
596        self.algo_cl_ord_id = Some(value.into());
597        self
598    }
599
600    /// Set a client request ID of up to 32 ASCII alphanumeric characters.
601    pub fn request_id(mut self, value: impl Into<String>) -> Self {
602        self.req_id = Some(value.into());
603        self
604    }
605
606    /// Amend the order size.
607    pub fn new_size(mut self, value: impl Into<String>) -> Self {
608        self.new_sz = Some(value.into());
609        self
610    }
611
612    /// Amend take-profit prices.
613    pub fn take_profit(
614        mut self,
615        trigger_px: impl Into<String>,
616        order_px: impl Into<String>,
617    ) -> Self {
618        self.new_tp_trigger_px = Some(trigger_px.into());
619        self.new_tp_ord_px = Some(order_px.into());
620        self
621    }
622
623    /// Set the amended take-profit trigger price source.
624    pub fn take_profit_price_type(mut self, value: impl Into<String>) -> Self {
625        self.new_tp_trigger_px_type = Some(value.into());
626        self
627    }
628
629    /// Delete the take-profit definition by sending the documented `0` sentinel.
630    pub fn delete_take_profit(mut self) -> Self {
631        self.new_tp_trigger_px = Some("0".to_owned());
632        self.new_tp_ord_px = None;
633        self.new_tp_trigger_px_type = None;
634        self
635    }
636
637    /// Amend stop-loss prices.
638    pub fn stop_loss(mut self, trigger_px: impl Into<String>, order_px: impl Into<String>) -> Self {
639        self.new_sl_trigger_px = Some(trigger_px.into());
640        self.new_sl_ord_px = Some(order_px.into());
641        self
642    }
643
644    /// Set the amended stop-loss trigger price source.
645    pub fn stop_loss_price_type(mut self, value: impl Into<String>) -> Self {
646        self.new_sl_trigger_px_type = Some(value.into());
647        self
648    }
649
650    /// Delete the stop-loss definition by sending the documented `0` sentinel.
651    pub fn delete_stop_loss(mut self) -> Self {
652        self.new_sl_trigger_px = Some("0".to_owned());
653        self.new_sl_ord_px = None;
654        self.new_sl_trigger_px_type = None;
655        self
656    }
657
658    /// Cancel the original order automatically when amendment fails.
659    pub fn cancel_on_fail(mut self, value: bool) -> Self {
660        self.cancel_on_fail = Some(value);
661        self
662    }
663}
664
665/// Query parameters shared by pending and historical algo orders.
666#[derive(Debug, Clone, Serialize)]
667pub struct AlgoOrderListRequest {
668    #[serde(rename = "ordType")]
669    ord_type: String,
670    #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
671    algo_id: Option<String>,
672    #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
673    inst_type: Option<String>,
674    #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
675    inst_id: Option<String>,
676    #[serde(skip_serializing_if = "Option::is_none")]
677    after: Option<String>,
678    #[serde(skip_serializing_if = "Option::is_none")]
679    before: Option<String>,
680    #[serde(skip_serializing_if = "Option::is_none")]
681    limit: Option<u32>,
682}
683
684impl AlgoOrderListRequest {
685    /// Create a query for one documented algo order type.
686    pub fn new(ord_type: impl Into<String>) -> Self {
687        Self {
688            ord_type: ord_type.into(),
689            algo_id: None,
690            inst_type: None,
691            inst_id: None,
692            after: None,
693            before: None,
694            limit: None,
695        }
696    }
697
698    /// Filter by algo ID.
699    pub fn algo_id(mut self, value: impl Into<String>) -> Self {
700        self.algo_id = Some(value.into());
701        self
702    }
703
704    /// Filter by instrument type.
705    pub fn inst_type(mut self, value: impl Into<String>) -> Self {
706        self.inst_type = Some(value.into());
707        self
708    }
709
710    /// Filter by instrument ID.
711    pub fn inst_id(mut self, value: impl Into<String>) -> Self {
712        self.inst_id = Some(value.into());
713        self
714    }
715
716    /// Return records before this algo-ID cursor.
717    pub fn after(mut self, value: impl Into<String>) -> Self {
718        self.after = Some(value.into());
719        self
720    }
721
722    /// Return records after this algo-ID cursor.
723    pub fn before(mut self, value: impl Into<String>) -> Self {
724        self.before = Some(value.into());
725        self
726    }
727
728    /// Set the number of rows, from 1 through 100.
729    pub fn limit(mut self, value: u32) -> Self {
730        self.limit = Some(value);
731        self
732    }
733}
734
735/// Query parameters for historical algo orders.
736#[derive(Debug, Clone, Serialize)]
737pub struct AlgoOrderHistoryRequest {
738    #[serde(flatten)]
739    common: AlgoOrderListRequest,
740    #[serde(skip_serializing_if = "Option::is_none")]
741    state: Option<String>,
742}
743
744impl AlgoOrderHistoryRequest {
745    /// Create a history query for one documented algo order type.
746    pub fn new(ord_type: impl Into<String>) -> Self {
747        Self {
748            common: AlgoOrderListRequest::new(ord_type),
749            state: None,
750        }
751    }
752
753    /// Filter by historical state.
754    pub fn state(mut self, value: impl Into<String>) -> Self {
755        self.state = Some(value.into());
756        self
757    }
758
759    /// Filter by OKX algo order ID.
760    pub fn algo_id(mut self, value: impl Into<String>) -> Self {
761        self.common = self.common.algo_id(value);
762        self
763    }
764
765    /// Filter by instrument type.
766    pub fn inst_type(mut self, value: impl Into<String>) -> Self {
767        self.common = self.common.inst_type(value);
768        self
769    }
770
771    /// Filter by instrument ID.
772    pub fn inst_id(mut self, value: impl Into<String>) -> Self {
773        self.common = self.common.inst_id(value);
774        self
775    }
776
777    /// Return records before this cursor.
778    pub fn after(mut self, value: impl Into<String>) -> Self {
779        self.common = self.common.after(value);
780        self
781    }
782
783    /// Return records after this cursor.
784    pub fn before(mut self, value: impl Into<String>) -> Self {
785        self.common = self.common.before(value);
786        self
787    }
788
789    /// Set the number of rows, from 1 through 100.
790    pub fn limit(mut self, value: u32) -> Self {
791        self.common = self.common.limit(value);
792        self
793    }
794}
795
796/// Query parameters for `GET /api/v5/trade/order-algo`.
797#[derive(Debug, Clone, Default, Serialize)]
798pub struct AlgoOrderDetailsRequest {
799    #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
800    algo_id: Option<String>,
801    #[serde(rename = "algoClOrdId", skip_serializing_if = "Option::is_none")]
802    algo_cl_ord_id: Option<String>,
803}
804
805impl AlgoOrderDetailsRequest {
806    /// Query by OKX algo ID.
807    pub fn by_algo_id(value: impl Into<String>) -> Self {
808        Self {
809            algo_id: Some(value.into()),
810            algo_cl_ord_id: None,
811        }
812    }
813
814    /// Query by client-supplied algo ID.
815    pub fn by_client_algo_order_id(value: impl Into<String>) -> Self {
816        Self {
817            algo_id: None,
818            algo_cl_ord_id: Some(value.into()),
819        }
820    }
821}
822
823#[cfg(test)]
824mod tests {
825    use super::*;
826
827    #[test]
828    fn twap_official_example_validates_and_serializes() {
829        let request = AlgoOrderRequest::new("BTC-USDT-SWAP", "cross", "buy", "twap", "10")
830            .position_side("net")
831            .twap_by_spread("10", "10", "100", "10");
832
833        assert_eq!(
834            serde_json::to_value(&request).unwrap(),
835            serde_json::json!({
836                "instId": "BTC-USDT-SWAP",
837                "tdMode": "cross",
838                "side": "buy",
839                "ordType": "twap",
840                "sz": "10",
841                "posSide": "net",
842                "pxSpread": "10",
843                "szLimit": "10",
844                "pxLimit": "100",
845                "timeInterval": "10"
846            })
847        );
848    }
849
850    #[test]
851    fn smart_iceberg_official_example_validates_and_serializes() {
852        let trigger = SmartIcebergTriggerRequest::new("price")
853            .price("90000")
854            .condition("cross_down");
855        let request = AlgoOrderRequest::new("BTC-USDT", "cash", "buy", "smart_iceberg", "100")
856            .smart_iceberg("10", "5", "conservative")
857            .price_limit("95000")
858            .smart_iceberg_triggers(vec![trigger]);
859
860        assert_eq!(
861            serde_json::to_value(&request).unwrap(),
862            serde_json::json!({
863                "instId": "BTC-USDT",
864                "tdMode": "cash",
865                "side": "buy",
866                "ordType": "smart_iceberg",
867                "sz": "100",
868                "szLimit": "10",
869                "pxLimit": "95000",
870                "lmtOrderNumber": "5",
871                "aggressiveness": "conservative",
872                "triggerParams": [{
873                    "triggerAction": "start",
874                    "triggerStrategy": "price",
875                    "triggerPx": "90000",
876                    "triggerCond": "cross_down"
877                }]
878            })
879        );
880    }
881
882    #[test]
883    fn amend_allows_both_identifiers_and_documented_delete_sentinel() {
884        let request = AmendAlgoOrderRequest::new("BTC-USDT")
885            .algo_id("1")
886            .client_algo_order_id("client1")
887            .delete_take_profit();
888        assert_eq!(
889            serde_json::to_value(&request).unwrap(),
890            serde_json::json!({
891                "instId": "BTC-USDT",
892                "algoId": "1",
893                "algoClOrdId": "client1",
894                "newTpTriggerPx": "0"
895            })
896        );
897    }
898}