Skip to main content

bybit_client/types/
trade.rs

1//! Trade-related types for order management.
2
3use serde::{Deserialize, Serialize};
4
5use crate::types::{
6    Category, OrderStatus, OrderType, PositionIdx, Side, StopOrderType, TimeInForce, TriggerBy,
7};
8
9
10/// Parameters for creating a new order.
11#[derive(Debug, Clone, Serialize)]
12#[serde(rename_all = "camelCase")]
13pub struct OrderParams {
14    /// Product category.
15    pub category: Category,
16    /// Trading symbol.
17    pub symbol: String,
18    /// Order side.
19    pub side: Side,
20    /// Order type.
21    pub order_type: OrderType,
22    /// Order quantity.
23    pub qty: String,
24    /// Whether to use leverage (spot margin only).
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub is_leverage: Option<i32>,
27    /// Market unit for market orders (baseCoin or quoteCoin).
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub market_unit: Option<String>,
30    /// Order price (required for limit orders).
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub price: Option<String>,
33    /// Trigger direction for conditional orders (1=rise, 2=fall).
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub trigger_direction: Option<i32>,
36    /// Order filter for conditional/stop orders.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub order_filter: Option<String>,
39    /// Trigger price for conditional orders.
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub trigger_price: Option<String>,
42    /// Trigger price type.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub trigger_by: Option<TriggerBy>,
45    /// Implied volatility (options only).
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub order_iv: Option<String>,
48    /// Time in force.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub time_in_force: Option<TimeInForce>,
51    /// Position index for hedge mode.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub position_idx: Option<PositionIdx>,
54    /// User-defined order ID.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub order_link_id: Option<String>,
57    /// Take profit price.
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub take_profit: Option<String>,
60    /// Stop loss price.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub stop_loss: Option<String>,
63    /// Take profit trigger price type.
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub tp_trigger_by: Option<TriggerBy>,
66    /// Stop loss trigger price type.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub sl_trigger_by: Option<TriggerBy>,
69    /// Reduce only order.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub reduce_only: Option<bool>,
72    /// Close on trigger.
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub close_on_trigger: Option<bool>,
75    /// Self-match prevention type.
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub smp_type: Option<String>,
78    /// Market maker protection.
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub mmp: Option<bool>,
81    /// TP/SL mode: Full or Partial.
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub tpsl_mode: Option<String>,
84    /// Take profit limit price (for partial TP/SL).
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub tp_limit_price: Option<String>,
87    /// Stop loss limit price (for partial TP/SL).
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub sl_limit_price: Option<String>,
90    /// Take profit order type.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub tp_order_type: Option<OrderType>,
93    /// Stop loss order type.
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub sl_order_type: Option<OrderType>,
96}
97
98impl OrderParams {
99    /// Create new order parameters.
100    pub fn new(
101        category: Category,
102        symbol: impl Into<String>,
103        side: Side,
104        order_type: OrderType,
105        qty: impl Into<String>,
106    ) -> Self {
107        Self {
108            category,
109            symbol: symbol.into(),
110            side,
111            order_type,
112            qty: qty.into(),
113            is_leverage: None,
114            market_unit: None,
115            price: None,
116            trigger_direction: None,
117            order_filter: None,
118            trigger_price: None,
119            trigger_by: None,
120            order_iv: None,
121            time_in_force: None,
122            position_idx: None,
123            order_link_id: None,
124            take_profit: None,
125            stop_loss: None,
126            tp_trigger_by: None,
127            sl_trigger_by: None,
128            reduce_only: None,
129            close_on_trigger: None,
130            smp_type: None,
131            mmp: None,
132            tpsl_mode: None,
133            tp_limit_price: None,
134            sl_limit_price: None,
135            tp_order_type: None,
136            sl_order_type: None,
137        }
138    }
139
140    /// Create a market order.
141    pub fn market(
142        category: Category,
143        symbol: impl Into<String>,
144        side: Side,
145        qty: impl Into<String>,
146    ) -> Self {
147        Self::new(category, symbol, side, OrderType::Market, qty)
148    }
149
150    /// Create a limit order.
151    pub fn limit(
152        category: Category,
153        symbol: impl Into<String>,
154        side: Side,
155        qty: impl Into<String>,
156        price: impl Into<String>,
157    ) -> Self {
158        Self::new(category, symbol, side, OrderType::Limit, qty).price(price)
159    }
160
161    /// Set the price.
162    pub fn price(mut self, price: impl Into<String>) -> Self {
163        self.price = Some(price.into());
164        self
165    }
166
167    /// Set time in force.
168    pub fn time_in_force(mut self, tif: TimeInForce) -> Self {
169        self.time_in_force = Some(tif);
170        self
171    }
172
173    /// Set user-defined order ID.
174    pub fn order_link_id(mut self, id: impl Into<String>) -> Self {
175        self.order_link_id = Some(id.into());
176        self
177    }
178
179    /// Set take profit price.
180    pub fn take_profit(mut self, tp: impl Into<String>) -> Self {
181        self.take_profit = Some(tp.into());
182        self
183    }
184
185    /// Set stop loss price.
186    pub fn stop_loss(mut self, sl: impl Into<String>) -> Self {
187        self.stop_loss = Some(sl.into());
188        self
189    }
190
191    /// Set reduce only.
192    pub fn reduce_only(mut self, reduce: bool) -> Self {
193        self.reduce_only = Some(reduce);
194        self
195    }
196
197    /// Set position index for hedge mode.
198    pub fn position_idx(mut self, idx: PositionIdx) -> Self {
199        self.position_idx = Some(idx);
200        self
201    }
202
203    /// Set leverage flag (spot margin).
204    pub fn leverage(mut self, enabled: bool) -> Self {
205        self.is_leverage = Some(if enabled { 1 } else { 0 });
206        self
207    }
208
209    /// Set trigger price for conditional orders.
210    pub fn trigger_price(mut self, price: impl Into<String>) -> Self {
211        self.trigger_price = Some(price.into());
212        self
213    }
214
215    /// Set trigger by type.
216    pub fn trigger_by(mut self, by: TriggerBy) -> Self {
217        self.trigger_by = Some(by);
218        self
219    }
220
221    /// Set close on trigger.
222    pub fn close_on_trigger(mut self, close: bool) -> Self {
223        self.close_on_trigger = Some(close);
224        self
225    }
226
227    /// Set TP/SL mode.
228    pub fn tpsl_mode(mut self, mode: impl Into<String>) -> Self {
229        self.tpsl_mode = Some(mode.into());
230        self
231    }
232}
233
234
235/// Parameters for amending an existing order.
236#[derive(Debug, Clone, Serialize)]
237#[serde(rename_all = "camelCase")]
238pub struct AmendOrderParams {
239    /// Product category.
240    pub category: Category,
241    /// Trading symbol.
242    pub symbol: String,
243    /// Order ID (either this or order_link_id required).
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub order_id: Option<String>,
246    /// User-defined order ID.
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub order_link_id: Option<String>,
249    /// Implied volatility (options).
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub order_iv: Option<String>,
252    /// New trigger price.
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub trigger_price: Option<String>,
255    /// New quantity.
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub qty: Option<String>,
258    /// New price.
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub price: Option<String>,
261    /// TP/SL mode.
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub tpsl_mode: Option<String>,
264    /// New take profit price.
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub take_profit: Option<String>,
267    /// New stop loss price.
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub stop_loss: Option<String>,
270    /// Take profit trigger type.
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub tp_trigger_by: Option<TriggerBy>,
273    /// Stop loss trigger type.
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub sl_trigger_by: Option<TriggerBy>,
276    /// Trigger price type.
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub trigger_by: Option<TriggerBy>,
279    /// Take profit limit price.
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub tp_limit_price: Option<String>,
282    /// Stop loss limit price.
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub sl_limit_price: Option<String>,
285}
286
287impl AmendOrderParams {
288    /// Create new amend order parameters using order ID.
289    pub fn by_order_id(
290        category: Category,
291        symbol: impl Into<String>,
292        order_id: impl Into<String>,
293    ) -> Self {
294        Self {
295            category,
296            symbol: symbol.into(),
297            order_id: Some(order_id.into()),
298            order_link_id: None,
299            order_iv: None,
300            trigger_price: None,
301            qty: None,
302            price: None,
303            tpsl_mode: None,
304            take_profit: None,
305            stop_loss: None,
306            tp_trigger_by: None,
307            sl_trigger_by: None,
308            trigger_by: None,
309            tp_limit_price: None,
310            sl_limit_price: None,
311        }
312    }
313
314    /// Create new amend order parameters using order link ID.
315    pub fn by_order_link_id(
316        category: Category,
317        symbol: impl Into<String>,
318        order_link_id: impl Into<String>,
319    ) -> Self {
320        Self {
321            category,
322            symbol: symbol.into(),
323            order_id: None,
324            order_link_id: Some(order_link_id.into()),
325            order_iv: None,
326            trigger_price: None,
327            qty: None,
328            price: None,
329            tpsl_mode: None,
330            take_profit: None,
331            stop_loss: None,
332            tp_trigger_by: None,
333            sl_trigger_by: None,
334            trigger_by: None,
335            tp_limit_price: None,
336            sl_limit_price: None,
337        }
338    }
339
340    /// Set new quantity.
341    pub fn qty(mut self, qty: impl Into<String>) -> Self {
342        self.qty = Some(qty.into());
343        self
344    }
345
346    /// Set new price.
347    pub fn price(mut self, price: impl Into<String>) -> Self {
348        self.price = Some(price.into());
349        self
350    }
351
352    /// Set new take profit.
353    pub fn take_profit(mut self, tp: impl Into<String>) -> Self {
354        self.take_profit = Some(tp.into());
355        self
356    }
357
358    /// Set new stop loss.
359    pub fn stop_loss(mut self, sl: impl Into<String>) -> Self {
360        self.stop_loss = Some(sl.into());
361        self
362    }
363
364    /// Set new trigger price.
365    pub fn trigger_price(mut self, price: impl Into<String>) -> Self {
366        self.trigger_price = Some(price.into());
367        self
368    }
369}
370
371
372/// Parameters for canceling an order.
373#[derive(Debug, Clone, Serialize)]
374#[serde(rename_all = "camelCase")]
375pub struct CancelOrderParams {
376    /// Product category.
377    pub category: Category,
378    /// Trading symbol.
379    pub symbol: String,
380    /// Order ID (either this or order_link_id required).
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub order_id: Option<String>,
383    /// User-defined order ID.
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub order_link_id: Option<String>,
386    /// Order filter for conditional orders.
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub order_filter: Option<String>,
389}
390
391impl CancelOrderParams {
392    /// Create new cancel parameters using order ID.
393    pub fn by_order_id(
394        category: Category,
395        symbol: impl Into<String>,
396        order_id: impl Into<String>,
397    ) -> Self {
398        Self {
399            category,
400            symbol: symbol.into(),
401            order_id: Some(order_id.into()),
402            order_link_id: None,
403            order_filter: None,
404        }
405    }
406
407    /// Create new cancel parameters using order link ID.
408    pub fn by_order_link_id(
409        category: Category,
410        symbol: impl Into<String>,
411        order_link_id: impl Into<String>,
412    ) -> Self {
413        Self {
414            category,
415            symbol: symbol.into(),
416            order_id: None,
417            order_link_id: Some(order_link_id.into()),
418            order_filter: None,
419        }
420    }
421
422    /// Set order filter.
423    pub fn order_filter(mut self, filter: impl Into<String>) -> Self {
424        self.order_filter = Some(filter.into());
425        self
426    }
427}
428
429
430/// Parameters for canceling all orders.
431#[derive(Debug, Clone, Serialize)]
432#[serde(rename_all = "camelCase")]
433pub struct CancelAllOrdersParams {
434    /// Product category.
435    pub category: Category,
436    /// Trading symbol (optional).
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub symbol: Option<String>,
439    /// Base coin filter.
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub base_coin: Option<String>,
442    /// Settle coin filter.
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub settle_coin: Option<String>,
445    /// Order filter.
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub order_filter: Option<String>,
448    /// Stop order type filter.
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub stop_order_type: Option<StopOrderType>,
451}
452
453impl CancelAllOrdersParams {
454    /// Create new cancel all parameters.
455    pub fn new(category: Category) -> Self {
456        Self {
457            category,
458            symbol: None,
459            base_coin: None,
460            settle_coin: None,
461            order_filter: None,
462            stop_order_type: None,
463        }
464    }
465
466    /// Set symbol filter.
467    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
468        self.symbol = Some(symbol.into());
469        self
470    }
471
472    /// Set base coin filter.
473    pub fn base_coin(mut self, coin: impl Into<String>) -> Self {
474        self.base_coin = Some(coin.into());
475        self
476    }
477
478    /// Set settle coin filter.
479    pub fn settle_coin(mut self, coin: impl Into<String>) -> Self {
480        self.settle_coin = Some(coin.into());
481        self
482    }
483}
484
485
486/// Parameters for getting open/active orders.
487#[derive(Debug, Clone, Serialize)]
488#[serde(rename_all = "camelCase")]
489pub struct GetOpenOrdersParams {
490    /// Product category.
491    pub category: Category,
492    /// Trading symbol.
493    #[serde(skip_serializing_if = "Option::is_none")]
494    pub symbol: Option<String>,
495    /// Base coin filter.
496    #[serde(skip_serializing_if = "Option::is_none")]
497    pub base_coin: Option<String>,
498    /// Settle coin filter.
499    #[serde(skip_serializing_if = "Option::is_none")]
500    pub settle_coin: Option<String>,
501    /// Order ID filter.
502    #[serde(skip_serializing_if = "Option::is_none")]
503    pub order_id: Option<String>,
504    /// Order link ID filter.
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub order_link_id: Option<String>,
507    /// Open only filter (0=all, 1=open only, 2=conditional only).
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub open_only: Option<i32>,
510    /// Order filter.
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub order_filter: Option<String>,
513    /// Limit (max 50).
514    #[serde(skip_serializing_if = "Option::is_none")]
515    pub limit: Option<u32>,
516    /// Cursor for pagination.
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub cursor: Option<String>,
519}
520
521impl GetOpenOrdersParams {
522    /// Create new parameters.
523    pub fn new(category: Category) -> Self {
524        Self {
525            category,
526            symbol: None,
527            base_coin: None,
528            settle_coin: None,
529            order_id: None,
530            order_link_id: None,
531            open_only: None,
532            order_filter: None,
533            limit: None,
534            cursor: None,
535        }
536    }
537
538    /// Set symbol filter.
539    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
540        self.symbol = Some(symbol.into());
541        self
542    }
543
544    /// Set base coin filter.
545    pub fn base_coin(mut self, coin: impl Into<String>) -> Self {
546        self.base_coin = Some(coin.into());
547        self
548    }
549
550    /// Set settle coin filter.
551    pub fn settle_coin(mut self, coin: impl Into<String>) -> Self {
552        self.settle_coin = Some(coin.into());
553        self
554    }
555
556    /// Set order ID filter.
557    pub fn order_id(mut self, id: impl Into<String>) -> Self {
558        self.order_id = Some(id.into());
559        self
560    }
561
562    /// Set order link ID filter.
563    pub fn order_link_id(mut self, id: impl Into<String>) -> Self {
564        self.order_link_id = Some(id.into());
565        self
566    }
567
568    /// Set limit.
569    pub fn limit(mut self, limit: u32) -> Self {
570        self.limit = Some(limit);
571        self
572    }
573
574    /// Set cursor.
575    pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
576        self.cursor = Some(cursor.into());
577        self
578    }
579}
580
581/// Parameters for getting order history.
582#[derive(Debug, Clone, Serialize)]
583#[serde(rename_all = "camelCase")]
584pub struct GetOrderHistoryParams {
585    /// Product category.
586    pub category: Category,
587    /// Trading symbol.
588    #[serde(skip_serializing_if = "Option::is_none")]
589    pub symbol: Option<String>,
590    /// Base coin filter.
591    #[serde(skip_serializing_if = "Option::is_none")]
592    pub base_coin: Option<String>,
593    /// Settle coin filter.
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub settle_coin: Option<String>,
596    /// Order ID filter.
597    #[serde(skip_serializing_if = "Option::is_none")]
598    pub order_id: Option<String>,
599    /// Order link ID filter.
600    #[serde(skip_serializing_if = "Option::is_none")]
601    pub order_link_id: Option<String>,
602    /// Order filter.
603    #[serde(skip_serializing_if = "Option::is_none")]
604    pub order_filter: Option<String>,
605    /// Order status filter.
606    #[serde(skip_serializing_if = "Option::is_none")]
607    pub order_status: Option<OrderStatus>,
608    /// Start time (ms).
609    #[serde(skip_serializing_if = "Option::is_none")]
610    pub start_time: Option<u64>,
611    /// End time (ms).
612    #[serde(skip_serializing_if = "Option::is_none")]
613    pub end_time: Option<u64>,
614    /// Limit (max 50).
615    #[serde(skip_serializing_if = "Option::is_none")]
616    pub limit: Option<u32>,
617    /// Cursor for pagination.
618    #[serde(skip_serializing_if = "Option::is_none")]
619    pub cursor: Option<String>,
620}
621
622impl GetOrderHistoryParams {
623    /// Create new parameters.
624    pub fn new(category: Category) -> Self {
625        Self {
626            category,
627            symbol: None,
628            base_coin: None,
629            settle_coin: None,
630            order_id: None,
631            order_link_id: None,
632            order_filter: None,
633            order_status: None,
634            start_time: None,
635            end_time: None,
636            limit: None,
637            cursor: None,
638        }
639    }
640
641    /// Set symbol filter.
642    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
643        self.symbol = Some(symbol.into());
644        self
645    }
646
647    /// Set base coin filter.
648    pub fn base_coin(mut self, coin: impl Into<String>) -> Self {
649        self.base_coin = Some(coin.into());
650        self
651    }
652
653    /// Set order status filter.
654    pub fn order_status(mut self, status: OrderStatus) -> Self {
655        self.order_status = Some(status);
656        self
657    }
658
659    /// Set start time.
660    pub fn start_time(mut self, start: u64) -> Self {
661        self.start_time = Some(start);
662        self
663    }
664
665    /// Set end time.
666    pub fn end_time(mut self, end: u64) -> Self {
667        self.end_time = Some(end);
668        self
669    }
670
671    /// Set limit.
672    pub fn limit(mut self, limit: u32) -> Self {
673        self.limit = Some(limit);
674        self
675    }
676
677    /// Set cursor.
678    pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
679        self.cursor = Some(cursor.into());
680        self
681    }
682}
683
684/// Parameters for getting execution/trade history.
685#[derive(Debug, Clone, Serialize)]
686#[serde(rename_all = "camelCase")]
687pub struct GetExecutionListParams {
688    /// Product category.
689    pub category: Category,
690    /// Trading symbol.
691    #[serde(skip_serializing_if = "Option::is_none")]
692    pub symbol: Option<String>,
693    /// Order ID filter.
694    #[serde(skip_serializing_if = "Option::is_none")]
695    pub order_id: Option<String>,
696    /// Order link ID filter.
697    #[serde(skip_serializing_if = "Option::is_none")]
698    pub order_link_id: Option<String>,
699    /// Base coin filter.
700    #[serde(skip_serializing_if = "Option::is_none")]
701    pub base_coin: Option<String>,
702    /// Start time (ms).
703    #[serde(skip_serializing_if = "Option::is_none")]
704    pub start_time: Option<u64>,
705    /// End time (ms).
706    #[serde(skip_serializing_if = "Option::is_none")]
707    pub end_time: Option<u64>,
708    /// Execution type filter.
709    #[serde(skip_serializing_if = "Option::is_none")]
710    pub exec_type: Option<String>,
711    /// Limit (max 100).
712    #[serde(skip_serializing_if = "Option::is_none")]
713    pub limit: Option<u32>,
714    /// Cursor for pagination.
715    #[serde(skip_serializing_if = "Option::is_none")]
716    pub cursor: Option<String>,
717}
718
719impl GetExecutionListParams {
720    /// Create new parameters.
721    pub fn new(category: Category) -> Self {
722        Self {
723            category,
724            symbol: None,
725            order_id: None,
726            order_link_id: None,
727            base_coin: None,
728            start_time: None,
729            end_time: None,
730            exec_type: None,
731            limit: None,
732            cursor: None,
733        }
734    }
735
736    /// Set symbol filter.
737    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
738        self.symbol = Some(symbol.into());
739        self
740    }
741
742    /// Set order ID filter.
743    pub fn order_id(mut self, id: impl Into<String>) -> Self {
744        self.order_id = Some(id.into());
745        self
746    }
747
748    /// Set base coin filter.
749    pub fn base_coin(mut self, coin: impl Into<String>) -> Self {
750        self.base_coin = Some(coin.into());
751        self
752    }
753
754    /// Set start time.
755    pub fn start_time(mut self, start: u64) -> Self {
756        self.start_time = Some(start);
757        self
758    }
759
760    /// Set end time.
761    pub fn end_time(mut self, end: u64) -> Self {
762        self.end_time = Some(end);
763        self
764    }
765
766    /// Set limit.
767    pub fn limit(mut self, limit: u32) -> Self {
768        self.limit = Some(limit);
769        self
770    }
771
772    /// Set cursor.
773    pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
774        self.cursor = Some(cursor.into());
775        self
776    }
777}
778
779/// Parameters for spot borrow check.
780#[derive(Debug, Clone, Serialize)]
781#[serde(rename_all = "camelCase")]
782pub struct GetBorrowQuotaParams {
783    /// Product category (must be spot).
784    pub category: Category,
785    /// Trading symbol.
786    pub symbol: String,
787    /// Order side.
788    pub side: Side,
789}
790
791impl GetBorrowQuotaParams {
792    /// Create new parameters.
793    pub fn new(symbol: impl Into<String>, side: Side) -> Self {
794        Self {
795            category: Category::Spot,
796            symbol: symbol.into(),
797            side,
798        }
799    }
800}
801
802
803/// Single order in a batch create request.
804#[derive(Debug, Clone, Serialize)]
805#[serde(rename_all = "camelCase")]
806pub struct BatchOrderParams {
807    /// Trading symbol.
808    pub symbol: String,
809    /// Order side.
810    pub side: Side,
811    /// Order type.
812    pub order_type: OrderType,
813    /// Order quantity.
814    pub qty: String,
815    /// Whether to use leverage (spot margin only).
816    #[serde(skip_serializing_if = "Option::is_none")]
817    pub is_leverage: Option<i32>,
818    /// Order price (required for limit orders).
819    #[serde(skip_serializing_if = "Option::is_none")]
820    pub price: Option<String>,
821    /// Trigger direction for conditional orders.
822    #[serde(skip_serializing_if = "Option::is_none")]
823    pub trigger_direction: Option<i32>,
824    /// Trigger price type.
825    #[serde(skip_serializing_if = "Option::is_none")]
826    pub trigger_by: Option<TriggerBy>,
827    /// Implied volatility (options only).
828    #[serde(skip_serializing_if = "Option::is_none")]
829    pub order_iv: Option<String>,
830    /// Time in force.
831    #[serde(skip_serializing_if = "Option::is_none")]
832    pub time_in_force: Option<TimeInForce>,
833    /// Position index for hedge mode.
834    #[serde(skip_serializing_if = "Option::is_none")]
835    pub position_idx: Option<PositionIdx>,
836    /// User-defined order ID.
837    #[serde(skip_serializing_if = "Option::is_none")]
838    pub order_link_id: Option<String>,
839    /// Take profit price.
840    #[serde(skip_serializing_if = "Option::is_none")]
841    pub take_profit: Option<String>,
842    /// Stop loss price.
843    #[serde(skip_serializing_if = "Option::is_none")]
844    pub stop_loss: Option<String>,
845    /// Take profit trigger type.
846    #[serde(skip_serializing_if = "Option::is_none")]
847    pub tp_trigger_by: Option<TriggerBy>,
848    /// Stop loss trigger type.
849    #[serde(skip_serializing_if = "Option::is_none")]
850    pub sl_trigger_by: Option<TriggerBy>,
851    /// Reduce only order.
852    #[serde(skip_serializing_if = "Option::is_none")]
853    pub reduce_only: Option<bool>,
854    /// Close on trigger.
855    #[serde(skip_serializing_if = "Option::is_none")]
856    pub close_on_trigger: Option<bool>,
857    /// Self-match prevention type.
858    #[serde(skip_serializing_if = "Option::is_none")]
859    pub smp_type: Option<String>,
860    /// Market maker protection.
861    #[serde(skip_serializing_if = "Option::is_none")]
862    pub mmp: Option<bool>,
863    /// TP/SL mode.
864    #[serde(skip_serializing_if = "Option::is_none")]
865    pub tpsl_mode: Option<String>,
866    /// Take profit limit price.
867    #[serde(skip_serializing_if = "Option::is_none")]
868    pub tp_limit_price: Option<String>,
869    /// Stop loss limit price.
870    #[serde(skip_serializing_if = "Option::is_none")]
871    pub sl_limit_price: Option<String>,
872    /// Take profit order type.
873    #[serde(skip_serializing_if = "Option::is_none")]
874    pub tp_order_type: Option<OrderType>,
875    /// Stop loss order type.
876    #[serde(skip_serializing_if = "Option::is_none")]
877    pub sl_order_type: Option<OrderType>,
878}
879
880impl BatchOrderParams {
881    /// Create new batch order parameters.
882    pub fn new(
883        symbol: impl Into<String>,
884        side: Side,
885        order_type: OrderType,
886        qty: impl Into<String>,
887    ) -> Self {
888        Self {
889            symbol: symbol.into(),
890            side,
891            order_type,
892            qty: qty.into(),
893            is_leverage: None,
894            price: None,
895            trigger_direction: None,
896            trigger_by: None,
897            order_iv: None,
898            time_in_force: None,
899            position_idx: None,
900            order_link_id: None,
901            take_profit: None,
902            stop_loss: None,
903            tp_trigger_by: None,
904            sl_trigger_by: None,
905            reduce_only: None,
906            close_on_trigger: None,
907            smp_type: None,
908            mmp: None,
909            tpsl_mode: None,
910            tp_limit_price: None,
911            sl_limit_price: None,
912            tp_order_type: None,
913            sl_order_type: None,
914        }
915    }
916
917    /// Create a market order for batch.
918    pub fn market(symbol: impl Into<String>, side: Side, qty: impl Into<String>) -> Self {
919        Self::new(symbol, side, OrderType::Market, qty)
920    }
921
922    /// Create a limit order for batch.
923    pub fn limit(
924        symbol: impl Into<String>,
925        side: Side,
926        qty: impl Into<String>,
927        price: impl Into<String>,
928    ) -> Self {
929        Self::new(symbol, side, OrderType::Limit, qty).price(price)
930    }
931
932    /// Set the price.
933    pub fn price(mut self, price: impl Into<String>) -> Self {
934        self.price = Some(price.into());
935        self
936    }
937
938    /// Set time in force.
939    pub fn time_in_force(mut self, tif: TimeInForce) -> Self {
940        self.time_in_force = Some(tif);
941        self
942    }
943
944    /// Set order link ID.
945    pub fn order_link_id(mut self, id: impl Into<String>) -> Self {
946        self.order_link_id = Some(id.into());
947        self
948    }
949
950    /// Set reduce only.
951    pub fn reduce_only(mut self, reduce: bool) -> Self {
952        self.reduce_only = Some(reduce);
953        self
954    }
955
956    /// Set position index.
957    pub fn position_idx(mut self, idx: PositionIdx) -> Self {
958        self.position_idx = Some(idx);
959        self
960    }
961}
962
963/// Single order to amend in a batch.
964#[derive(Debug, Clone, Serialize)]
965#[serde(rename_all = "camelCase")]
966pub struct BatchAmendOrderParams {
967    /// Trading symbol.
968    pub symbol: String,
969    /// Order ID.
970    #[serde(skip_serializing_if = "Option::is_none")]
971    pub order_id: Option<String>,
972    /// User-defined order ID.
973    #[serde(skip_serializing_if = "Option::is_none")]
974    pub order_link_id: Option<String>,
975    /// Implied volatility.
976    #[serde(skip_serializing_if = "Option::is_none")]
977    pub order_iv: Option<String>,
978    /// Trigger price.
979    #[serde(skip_serializing_if = "Option::is_none")]
980    pub trigger_price: Option<String>,
981    /// New quantity.
982    #[serde(skip_serializing_if = "Option::is_none")]
983    pub qty: Option<String>,
984    /// New price.
985    #[serde(skip_serializing_if = "Option::is_none")]
986    pub price: Option<String>,
987    /// TP/SL mode.
988    #[serde(skip_serializing_if = "Option::is_none")]
989    pub tpsl_mode: Option<String>,
990    /// Take profit price.
991    #[serde(skip_serializing_if = "Option::is_none")]
992    pub take_profit: Option<String>,
993    /// Stop loss price.
994    #[serde(skip_serializing_if = "Option::is_none")]
995    pub stop_loss: Option<String>,
996    /// Take profit trigger type.
997    #[serde(skip_serializing_if = "Option::is_none")]
998    pub tp_trigger_by: Option<TriggerBy>,
999    /// Stop loss trigger type.
1000    #[serde(skip_serializing_if = "Option::is_none")]
1001    pub sl_trigger_by: Option<TriggerBy>,
1002    /// Trigger type.
1003    #[serde(skip_serializing_if = "Option::is_none")]
1004    pub trigger_by: Option<TriggerBy>,
1005    /// Take profit limit price.
1006    #[serde(skip_serializing_if = "Option::is_none")]
1007    pub tp_limit_price: Option<String>,
1008    /// Stop loss limit price.
1009    #[serde(skip_serializing_if = "Option::is_none")]
1010    pub sl_limit_price: Option<String>,
1011}
1012
1013impl BatchAmendOrderParams {
1014    /// Create using order ID.
1015    pub fn by_order_id(symbol: impl Into<String>, order_id: impl Into<String>) -> Self {
1016        Self {
1017            symbol: symbol.into(),
1018            order_id: Some(order_id.into()),
1019            order_link_id: None,
1020            order_iv: None,
1021            trigger_price: None,
1022            qty: None,
1023            price: None,
1024            tpsl_mode: None,
1025            take_profit: None,
1026            stop_loss: None,
1027            tp_trigger_by: None,
1028            sl_trigger_by: None,
1029            trigger_by: None,
1030            tp_limit_price: None,
1031            sl_limit_price: None,
1032        }
1033    }
1034
1035    /// Create using order link ID.
1036    pub fn by_order_link_id(symbol: impl Into<String>, order_link_id: impl Into<String>) -> Self {
1037        Self {
1038            symbol: symbol.into(),
1039            order_id: None,
1040            order_link_id: Some(order_link_id.into()),
1041            order_iv: None,
1042            trigger_price: None,
1043            qty: None,
1044            price: None,
1045            tpsl_mode: None,
1046            take_profit: None,
1047            stop_loss: None,
1048            tp_trigger_by: None,
1049            sl_trigger_by: None,
1050            trigger_by: None,
1051            tp_limit_price: None,
1052            sl_limit_price: None,
1053        }
1054    }
1055
1056    /// Set new quantity.
1057    pub fn qty(mut self, qty: impl Into<String>) -> Self {
1058        self.qty = Some(qty.into());
1059        self
1060    }
1061
1062    /// Set new price.
1063    pub fn price(mut self, price: impl Into<String>) -> Self {
1064        self.price = Some(price.into());
1065        self
1066    }
1067}
1068
1069/// Single order to cancel in a batch.
1070#[derive(Debug, Clone, Serialize)]
1071#[serde(rename_all = "camelCase")]
1072pub struct BatchCancelOrderParams {
1073    /// Trading symbol.
1074    pub symbol: String,
1075    /// Order ID.
1076    #[serde(skip_serializing_if = "Option::is_none")]
1077    pub order_id: Option<String>,
1078    /// User-defined order ID.
1079    #[serde(skip_serializing_if = "Option::is_none")]
1080    pub order_link_id: Option<String>,
1081}
1082
1083impl BatchCancelOrderParams {
1084    /// Create using order ID.
1085    pub fn by_order_id(symbol: impl Into<String>, order_id: impl Into<String>) -> Self {
1086        Self {
1087            symbol: symbol.into(),
1088            order_id: Some(order_id.into()),
1089            order_link_id: None,
1090        }
1091    }
1092
1093    /// Create using order link ID.
1094    pub fn by_order_link_id(symbol: impl Into<String>, order_link_id: impl Into<String>) -> Self {
1095        Self {
1096            symbol: symbol.into(),
1097            order_id: None,
1098            order_link_id: Some(order_link_id.into()),
1099        }
1100    }
1101}
1102
1103
1104/// Result of order creation/amendment/cancellation.
1105#[derive(Debug, Clone, Deserialize)]
1106#[serde(rename_all = "camelCase")]
1107pub struct OrderResult {
1108    /// Order ID assigned by the exchange.
1109    pub order_id: String,
1110    /// User-defined order ID.
1111    pub order_link_id: String,
1112}
1113
1114/// Detailed order information.
1115#[derive(Debug, Clone, Deserialize)]
1116#[serde(rename_all = "camelCase")]
1117pub struct OrderInfo {
1118    /// Order ID.
1119    pub order_id: String,
1120    /// User-defined order ID.
1121    pub order_link_id: String,
1122    /// Block trade ID (if applicable).
1123    #[serde(default)]
1124    pub block_trade_id: Option<String>,
1125    /// Trading symbol.
1126    pub symbol: String,
1127    /// Order price.
1128    pub price: String,
1129    /// Order quantity.
1130    pub qty: String,
1131    /// Order side.
1132    pub side: Side,
1133    /// Whether leverage is used (spot margin).
1134    #[serde(default)]
1135    pub is_leverage: Option<String>,
1136    /// Position index.
1137    #[serde(default)]
1138    pub position_idx: Option<i32>,
1139    /// Order status.
1140    pub order_status: OrderStatus,
1141    /// Create type.
1142    #[serde(default)]
1143    pub create_type: Option<String>,
1144    /// Cancel type.
1145    #[serde(default)]
1146    pub cancel_type: Option<String>,
1147    /// Reject reason.
1148    #[serde(default)]
1149    pub reject_reason: Option<String>,
1150    /// Average filled price.
1151    #[serde(default)]
1152    pub avg_price: Option<String>,
1153    /// Remaining unfilled quantity.
1154    #[serde(default)]
1155    pub leaves_qty: Option<String>,
1156    /// Remaining unfilled value.
1157    #[serde(default)]
1158    pub leaves_value: Option<String>,
1159    /// Cumulative executed quantity.
1160    pub cum_exec_qty: String,
1161    /// Cumulative executed value.
1162    pub cum_exec_value: String,
1163    /// Cumulative executed fee.
1164    pub cum_exec_fee: String,
1165    /// Time in force.
1166    #[serde(default)]
1167    pub time_in_force: Option<TimeInForce>,
1168    /// Order type.
1169    pub order_type: OrderType,
1170    /// Stop order type.
1171    #[serde(default)]
1172    pub stop_order_type: Option<String>,
1173    /// Implied volatility.
1174    #[serde(default)]
1175    pub order_iv: Option<String>,
1176    /// Market unit.
1177    #[serde(default)]
1178    pub market_unit: Option<String>,
1179    /// Trigger price.
1180    #[serde(default)]
1181    pub trigger_price: Option<String>,
1182    /// Take profit price.
1183    #[serde(default)]
1184    pub take_profit: Option<String>,
1185    /// Stop loss price.
1186    #[serde(default)]
1187    pub stop_loss: Option<String>,
1188    /// TP/SL mode.
1189    #[serde(default)]
1190    pub tpsl_mode: Option<String>,
1191    /// Take profit limit price.
1192    #[serde(default)]
1193    pub tp_limit_price: Option<String>,
1194    /// Stop loss limit price.
1195    #[serde(default)]
1196    pub sl_limit_price: Option<String>,
1197    /// Take profit trigger type.
1198    #[serde(default)]
1199    pub tp_trigger_by: Option<String>,
1200    /// Stop loss trigger type.
1201    #[serde(default)]
1202    pub sl_trigger_by: Option<String>,
1203    /// Trigger direction.
1204    #[serde(default)]
1205    pub trigger_direction: Option<i32>,
1206    /// Trigger type.
1207    #[serde(default)]
1208    pub trigger_by: Option<String>,
1209    /// Last price at creation.
1210    #[serde(default)]
1211    pub last_price_on_created: Option<String>,
1212    /// Reduce only flag.
1213    #[serde(default)]
1214    pub reduce_only: Option<bool>,
1215    /// Close on trigger flag.
1216    #[serde(default)]
1217    pub close_on_trigger: Option<bool>,
1218    /// SMP type.
1219    #[serde(default)]
1220    pub smp_type: Option<String>,
1221    /// SMP group.
1222    #[serde(default)]
1223    pub smp_group: Option<i32>,
1224    /// SMP order ID.
1225    #[serde(default)]
1226    pub smp_order_id: Option<String>,
1227    /// Created time (ms).
1228    pub created_time: String,
1229    /// Updated time (ms).
1230    pub updated_time: String,
1231}
1232
1233/// Order list result (paginated).
1234#[derive(Debug, Clone, Deserialize)]
1235#[serde(rename_all = "camelCase")]
1236pub struct OrderListResult {
1237    /// Product category.
1238    pub category: Category,
1239    /// List of orders.
1240    pub list: Vec<OrderInfo>,
1241    /// Cursor for next page.
1242    #[serde(default)]
1243    pub next_page_cursor: Option<String>,
1244}
1245
1246/// Trade execution record.
1247#[derive(Debug, Clone, Deserialize)]
1248#[serde(rename_all = "camelCase")]
1249pub struct Execution {
1250    /// Trading symbol.
1251    pub symbol: String,
1252    /// Order ID.
1253    pub order_id: String,
1254    /// User-defined order ID.
1255    pub order_link_id: String,
1256    /// Order side.
1257    pub side: Side,
1258    /// Order price.
1259    pub order_price: String,
1260    /// Order quantity.
1261    pub order_qty: String,
1262    /// Remaining quantity.
1263    pub leaves_qty: String,
1264    /// Order type.
1265    pub order_type: OrderType,
1266    /// Stop order type.
1267    #[serde(default)]
1268    pub stop_order_type: Option<String>,
1269    /// Execution fee.
1270    pub exec_fee: String,
1271    /// Execution ID.
1272    pub exec_id: String,
1273    /// Execution price.
1274    pub exec_price: String,
1275    /// Execution quantity.
1276    pub exec_qty: String,
1277    /// Execution type.
1278    pub exec_type: String,
1279    /// Execution value.
1280    pub exec_value: String,
1281    /// Execution time (ms).
1282    pub exec_time: String,
1283    /// Is maker.
1284    pub is_maker: bool,
1285    /// Fee rate.
1286    #[serde(default)]
1287    pub fee_rate: Option<String>,
1288    /// Trade IV (options).
1289    #[serde(default)]
1290    pub trade_iv: Option<String>,
1291    /// Mark IV (options).
1292    #[serde(default)]
1293    pub mark_iv: Option<String>,
1294    /// Mark price.
1295    #[serde(default)]
1296    pub mark_price: Option<String>,
1297    /// Index price.
1298    #[serde(default)]
1299    pub index_price: Option<String>,
1300    /// Underlying price (options).
1301    #[serde(default)]
1302    pub underlying_price: Option<String>,
1303    /// Block trade ID.
1304    #[serde(default)]
1305    pub block_trade_id: Option<String>,
1306    /// Closed size (for closed positions).
1307    #[serde(default)]
1308    pub closed_size: Option<String>,
1309    /// Sequence number.
1310    #[serde(default)]
1311    pub seq: Option<i64>,
1312}
1313
1314/// Execution list result (paginated).
1315#[derive(Debug, Clone, Deserialize)]
1316#[serde(rename_all = "camelCase")]
1317pub struct ExecutionListResult {
1318    /// Product category.
1319    pub category: Category,
1320    /// List of executions.
1321    pub list: Vec<Execution>,
1322    /// Cursor for next page.
1323    #[serde(default)]
1324    pub next_page_cursor: Option<String>,
1325}
1326
1327/// Spot borrow quota check result.
1328#[derive(Debug, Clone, Deserialize)]
1329#[serde(rename_all = "camelCase")]
1330pub struct BorrowQuotaResult {
1331    /// Trading symbol.
1332    pub symbol: String,
1333    /// Order side.
1334    pub side: Side,
1335    /// Maximum trade quantity.
1336    pub max_trade_qty: String,
1337    /// Maximum trade amount.
1338    pub max_trade_amount: String,
1339    /// Spot max trade quantity.
1340    #[serde(default)]
1341    pub spot_max_trade_qty: Option<String>,
1342    /// Spot max trade amount.
1343    #[serde(default)]
1344    pub spot_max_trade_amount: Option<String>,
1345    /// Borrow coin.
1346    #[serde(default)]
1347    pub borrow_coin: Option<String>,
1348}
1349
1350/// Batch order result.
1351#[derive(Debug, Clone, Deserialize)]
1352#[serde(rename_all = "camelCase")]
1353pub struct BatchOrderResult {
1354    /// Product category.
1355    pub category: Category,
1356    /// Trading symbol.
1357    pub symbol: String,
1358    /// Order ID.
1359    pub order_id: String,
1360    /// User-defined order ID.
1361    pub order_link_id: String,
1362    /// Created at (for batch create).
1363    #[serde(default)]
1364    pub create_at: Option<String>,
1365}
1366
1367/// Batch operation result with error info.
1368#[derive(Debug, Clone, Deserialize)]
1369#[serde(rename_all = "camelCase")]
1370pub struct BatchOperationResult {
1371    /// List of successful results.
1372    pub list: Vec<BatchOrderResult>,
1373    /// Extended info with errors.
1374    #[serde(default)]
1375    pub ret_ext_info: Option<BatchRetExtInfo>,
1376}
1377
1378/// Extended error info for batch operations.
1379#[derive(Debug, Clone, Deserialize)]
1380#[serde(rename_all = "camelCase")]
1381pub struct BatchRetExtInfo {
1382    /// List of error codes and messages.
1383    pub list: Vec<BatchErrorInfo>,
1384}
1385
1386/// Error info for a single item in a batch operation.
1387#[derive(Debug, Clone, Deserialize)]
1388#[serde(rename_all = "camelCase")]
1389pub struct BatchErrorInfo {
1390    /// Error code.
1391    pub code: i32,
1392    /// Error message.
1393    pub msg: String,
1394}
1395
1396/// Cancel all orders result.
1397#[derive(Debug, Clone, Deserialize)]
1398#[serde(rename_all = "camelCase")]
1399pub struct CancelAllResult {
1400    /// List of cancelled orders.
1401    pub list: Vec<OrderResult>,
1402    /// Success status (for some responses).
1403    #[serde(default)]
1404    pub success: Option<String>,
1405}
1406
1407#[cfg(test)]
1408mod tests {
1409    use super::*;
1410
1411    #[test]
1412    fn test_order_params_market() {
1413        let params = OrderParams::market(Category::Linear, "BTCUSDT", Side::Buy, "0.001");
1414        assert_eq!(params.category, Category::Linear);
1415        assert_eq!(params.symbol, "BTCUSDT");
1416        assert_eq!(params.side, Side::Buy);
1417        assert_eq!(params.order_type, OrderType::Market);
1418        assert_eq!(params.qty, "0.001");
1419    }
1420
1421    #[test]
1422    fn test_order_params_limit() {
1423        let params = OrderParams::limit(Category::Spot, "BTCUSDT", Side::Sell, "0.5", "50000")
1424            .time_in_force(TimeInForce::GTC)
1425            .order_link_id("my_order_123");
1426
1427        assert_eq!(params.order_type, OrderType::Limit);
1428        assert_eq!(params.price, Some("50000".to_string()));
1429        assert_eq!(params.time_in_force, Some(TimeInForce::GTC));
1430        assert_eq!(params.order_link_id, Some("my_order_123".to_string()));
1431    }
1432
1433    #[test]
1434    fn test_amend_order_params() {
1435        let params = AmendOrderParams::by_order_id(Category::Linear, "BTCUSDT", "order123")
1436            .price("51000")
1437            .qty("0.002");
1438
1439        assert_eq!(params.order_id, Some("order123".to_string()));
1440        assert_eq!(params.price, Some("51000".to_string()));
1441        assert_eq!(params.qty, Some("0.002".to_string()));
1442    }
1443
1444    #[test]
1445    fn test_cancel_order_params() {
1446        let params =
1447            CancelOrderParams::by_order_link_id(Category::Spot, "ETHUSDT", "my_order_456");
1448
1449        assert_eq!(params.order_link_id, Some("my_order_456".to_string()));
1450        assert!(params.order_id.is_none());
1451    }
1452
1453    #[test]
1454    fn test_batch_order_params() {
1455        let order1 = BatchOrderParams::limit("BTCUSDT", Side::Buy, "0.001", "50000")
1456            .order_link_id("batch_1");
1457        let order2 = BatchOrderParams::market("ETHUSDT", Side::Sell, "0.1")
1458            .reduce_only(true);
1459
1460        assert_eq!(order1.price, Some("50000".to_string()));
1461        assert_eq!(order2.reduce_only, Some(true));
1462    }
1463}