Skip to main content

bybit/http/
orders.rs

1use rust_decimal::{Decimal, serde::str_option::deserialize as option_decimal};
2use serde::{Deserialize, Serialize};
3use serde_aux::prelude::deserialize_number_from_string as number;
4
5use super::common::List;
6use crate::{
7    CancelType, CreateType, OcoTriggerBy, OrderStatus, OrderType, PlaceType, PositionIdx,
8    RejectReason, Side, SmpType, TimeInForce, Timestamp, TpslMode, TriggerBy, TriggerDirection,
9    enums::{Category, StopOrderType},
10    serde::{empty_string_as_none, string_to_option_bool},
11    ws::OrderMsg,
12};
13
14/// Query parameters of `GET /v5/order/realtime` ([`Client::get_open_closed_orders`](crate::http::Client::get_open_closed_orders)).
15#[derive(Clone, Debug, Serialize)]
16#[serde(rename_all = "camelCase")]
17pub struct GetOpenClosedOrdersParams {
18    /// Product type
19    /// UTA2.0, UTA1.0: linear, inverse, spot, option
20    /// classic account: linear, inverse, spot
21    pub category: Category,
22    /// Symbol name, like BTCUSDT, uppercase only. For linear, either symbol, baseCoin, settleCoin is required
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub symbol: Option<String>,
25    /// Base coin, uppercase only
26    /// Supports linear, inverse & option
27    /// option: it returns all option open orders by default
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub base_coin: Option<String>,
30    /// Settle coin, uppercase only
31    /// linear: either symbol, baseCoin or settleCoin is required
32    /// spot: not supported
33    /// option: USDT or USDC
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub settle_coin: Option<String>,
36    /// Order ID
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub order_id: Option<String>,
39    /// User customized order ID
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub order_link_id: Option<String>,
42    /// 0(default): UTA2.0, UTA1.0, classic account query open status orders (e.g., New, PartiallyFilled) only
43    /// 1: UTA2.0, UTA1.0(except inverse)
44    /// 2: UTA1.0(inverse), classic account
45    /// Query a maximum of recent 500 closed status records are kept under each account each category (e.g., Cancelled, Rejected, Filled orders).
46    /// If the Bybit service is restarted due to an update, this part of the data will be cleared and accumulated again, but the order records will still be queried in order history
47    /// openOnly param will be ignored when query by orderId or orderLinkId
48    /// Classic spot: not supported
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub open_only: Option<i32>,
51    /// Order: active order,
52    /// StopOrder: conditional order for Futures and Spot,
53    /// tpslOrder: spot TP/SL order,
54    /// OcoOrder: Spot oco order,
55    /// BidirectionalTpslOrder: Spot bidirectional TPSL order
56    /// - classic account spot: return Order active order by default
57    /// - Others: all kinds of orders by default
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub order_filter: Option<OrderFilter>,
60    /// Limit for data size per page. [1, 50]. Default: 20
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub limit: Option<i32>,
63    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub cursor: Option<String>,
66}
67
68impl GetOpenClosedOrdersParams {
69    /// Create with the required fields; set the optional ones with the `with_*` methods.
70    pub fn new(category: Category) -> Self {
71        Self {
72            category,
73            symbol: None,
74            base_coin: None,
75            settle_coin: None,
76            order_id: None,
77            order_link_id: None,
78            open_only: None,
79            order_filter: None,
80            limit: None,
81            cursor: None,
82        }
83    }
84
85    /// Set `symbol`: symbol name, like BTCUSDT, uppercase only. For linear, either symbol, baseCoin, settleCoin is required.
86    pub fn with_symbol(mut self, v: impl Into<String>) -> Self {
87        self.symbol = Some(v.into());
88        self
89    }
90    /// Set `base_coin`: base coin, uppercase only Supports linear, inverse & option option: it returns all option open orders by default.
91    pub fn with_base_coin(mut self, v: impl Into<String>) -> Self {
92        self.base_coin = Some(v.into());
93        self
94    }
95    /// Set `settle_coin`: settle coin, uppercase only linear: either symbol, baseCoin or settleCoin is required spot: not supported option: USDT or USDC.
96    pub fn with_settle_coin(mut self, v: impl Into<String>) -> Self {
97        self.settle_coin = Some(v.into());
98        self
99    }
100    /// Set `order_id`: order ID.
101    pub fn with_order_id(mut self, v: impl Into<String>) -> Self {
102        self.order_id = Some(v.into());
103        self
104    }
105    /// Set `order_link_id`: user customized order ID.
106    pub fn with_order_link_id(mut self, v: impl Into<String>) -> Self {
107        self.order_link_id = Some(v.into());
108        self
109    }
110    /// Set `open_only`: 0(default): UTA2.0, UTA1.0, classic account query open status orders (e.g., New, PartiallyFilled) only 1: UTA2.0, UTA1.0(except inverse) 2: UTA1.0(inverse), classic account Query a maximum of recent 500 closed status records are kept under ...
111    pub fn with_open_only(mut self, v: i32) -> Self {
112        self.open_only = Some(v);
113        self
114    }
115    /// Set `order_filter`: order: active order, StopOrder: conditional order for Futures and Spot, tpslOrder: spot TP/SL order, OcoOrder: Spot oco order, BidirectionalTpslOrder: Spot bidirectional TPSL order - classic account spot: return Order active order by ...
116    pub fn with_order_filter(mut self, v: OrderFilter) -> Self {
117        self.order_filter = Some(v);
118        self
119    }
120    /// Set `limit`: limit for data size per page. [1, 50]. Default: 20.
121    pub fn with_limit(mut self, v: i32) -> Self {
122        self.limit = Some(v);
123        self
124    }
125    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
126    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
127        self.cursor = Some(v.into());
128        self
129    }
130}
131
132/// Part of the request of `/v5/order/cancel`.
133#[derive(Debug, Clone, Serialize)]
134pub enum OrderFilter {
135    /// active order,
136    Order,
137    /// conditional order for Futures and Spot,
138    StopOrder,
139    /// spot TP/SL order,
140    #[serde(rename = "camelCase")]
141    TpslOrder,
142    /// Spot oco order,
143    OcoOrder,
144    /// Spot bidirectional TPSL order
145    /// - classic account spot: return Order active order by default
146    /// - Others: all kinds of orders by default
147    BidirectionalTpslOrder,
148}
149
150/// Item of the list returned by `GET /v5/order/realtime` ([`Client::get_open_closed_orders`](crate::http::Client::get_open_closed_orders), [`Client::get_order_history`](crate::http::Client::get_order_history)).
151#[derive(Debug, Deserialize, PartialEq, Clone)]
152#[serde(rename_all = "camelCase")]
153pub struct Order {
154    /// Order ID
155    pub order_id: String,
156    /// User customized order ID
157    #[serde(default, deserialize_with = "empty_string_as_none")]
158    pub order_link_id: Option<String>,
159    /// Paradigm block trade ID
160    #[serde(default, deserialize_with = "empty_string_as_none")]
161    pub block_trade_id: Option<String>,
162    /// Symbol name
163    pub symbol: String,
164    /// Order price
165    pub price: Decimal,
166    /// Order qty
167    pub qty: Decimal,
168    /// Side. Buy,Sell
169    pub side: Side,
170    /// Whether to borrow. Unified spot only. 0: false, 1: true. Classic spot is not supported, always 0
171    #[serde(default, deserialize_with = "string_to_option_bool")]
172    pub is_leverage: Option<bool>,
173    /// Position index. Used to identify positions in different position modes.
174    pub position_idx: PositionIdx,
175    /// Order status
176    pub order_status: OrderStatus,
177    /// Order create type
178    /// Only for category=linear or inverse
179    /// Spot, Option do not have this key
180    #[serde(default, deserialize_with = "empty_string_as_none")]
181    pub create_type: Option<CreateType>,
182    /// Cancel type
183    pub cancel_type: CancelType,
184    /// Reject reason. Classic spot is not supported
185    pub reject_reason: RejectReason,
186    /// Average filled price
187    /// UTA: returns "" for those orders without avg price
188    /// classic account: returns "0" for those orders without avg price, and also for those orders have partially filled but cancelled at the end
189    #[serde(default, deserialize_with = "option_decimal")]
190    pub avg_price: Option<Decimal>,
191    /// The remaining qty not executed. Classic spot is not supported
192    pub leaves_qty: Decimal,
193    /// The estimated value not executed. Classic spot is not supported
194    pub leaves_value: Decimal,
195    /// Cumulative executed order qty
196    pub cum_exec_qty: Decimal,
197    /// Cumulative executed order value. Classic spot is not supported
198    pub cum_exec_value: Decimal,
199    /// Cumulative executed trading fee. Classic spot is not supported
200    pub cum_exec_fee: Decimal,
201    /// Time in force
202    pub time_in_force: TimeInForce,
203    /// Order type. Market,Limit. For TP/SL order, it means the order type after triggered
204    pub order_type: OrderType,
205    /// Stop order type
206    #[serde(default, deserialize_with = "empty_string_as_none")]
207    pub stop_order_type: Option<StopOrderType>,
208    /// Implied volatility
209    #[serde(default, deserialize_with = "option_decimal")]
210    pub order_iv: Option<Decimal>,
211    /// The unit for qty when create Spot market orders for UTA account. baseCoin, quoteCoin
212    #[serde(default, deserialize_with = "empty_string_as_none")]
213    pub market_unit: Option<String>,
214    /// Trigger price. If stopOrderType=TrailingStop, it is activate price. Otherwise, it is trigger price
215    #[serde(default, deserialize_with = "option_decimal")]
216    pub trigger_price: Option<Decimal>,
217    /// Take profit price
218    #[serde(default, deserialize_with = "option_decimal")]
219    pub take_profit: Option<Decimal>,
220    /// Stop loss price
221    #[serde(default, deserialize_with = "option_decimal")]
222    pub stop_loss: Option<Decimal>,
223    /// TP/SL mode, Full: entire position for TP/SL. Partial: partial position tp/sl. Spot does not have this field, and Option returns always ""
224    #[serde(default, deserialize_with = "empty_string_as_none")]
225    pub tpsl_mode: Option<TpslMode>,
226    /// The trigger type of Spot OCO order.OcoTriggerByUnknown, OcoTriggerByTp, OcoTriggerByBySl. Classic spot is not supported
227    #[serde(default, deserialize_with = "empty_string_as_none")]
228    pub oco_trigger_by: Option<OcoTriggerBy>,
229    /// The limit order price when take profit price is triggered
230    #[serde(default, deserialize_with = "option_decimal")]
231    pub tp_limit_price: Option<Decimal>,
232    /// The limit order price when stop loss price is triggered
233    #[serde(default, deserialize_with = "option_decimal")]
234    pub sl_limit_price: Option<Decimal>,
235    /// The price type to trigger take profit
236    #[serde(default, deserialize_with = "empty_string_as_none")]
237    pub tp_trigger_by: Option<TriggerBy>,
238    /// The price type to trigger stop loss
239    #[serde(default, deserialize_with = "empty_string_as_none")]
240    pub sl_trigger_by: Option<TriggerBy>,
241    /// Trigger direction. 1: rise, 2: fall
242    pub trigger_direction: TriggerDirection,
243    /// The price type of trigger price
244    #[serde(default, deserialize_with = "empty_string_as_none")]
245    pub trigger_by: Option<TriggerBy>,
246    /// Last price when place the order, Spot is not applicable
247    #[serde(default, deserialize_with = "option_decimal")]
248    pub last_price_on_created: Option<Decimal>,
249    /// Last price when place the order, Spot has this field only
250    #[serde(default, deserialize_with = "option_decimal")]
251    pub base_price: Option<Decimal>,
252    /// Reduce only. true means reduce position size
253    pub reduce_only: bool,
254    /// Close on trigger. What is a close on trigger order?
255    pub close_on_trigger: bool,
256    /// Place type, option used. iv, price
257    #[serde(default, deserialize_with = "empty_string_as_none")]
258    pub place_type: Option<PlaceType>,
259    /// SMP execution type
260    pub smp_type: SmpType,
261    /// Smp group ID. If the UID has no group, it is 0 by default
262    pub smp_group: i64,
263    /// The counterparty's orderID which triggers this SMP execution
264    #[serde(default, deserialize_with = "empty_string_as_none")]
265    pub smp_order_id: Option<String>,
266    /// Order created timestamp (ms)
267    #[serde(deserialize_with = "number")]
268    pub created_time: Timestamp,
269    /// Order updated timestamp (ms)
270    #[serde(deserialize_with = "number")]
271    pub updated_time: Timestamp,
272}
273
274impl Order {
275    /// The order is still active (see [`OrderStatus::is_open`](crate::OrderStatus::is_open)).
276    pub fn is_open_status(&self) -> bool {
277        self.order_status.is_open()
278    }
279    /// The order reached a final status.
280    pub fn is_closed_status(&self) -> bool {
281        self.order_status.is_closed()
282    }
283    /// Apply an `order` stream update; `leavesQty`/`leavesValue` keep their value when the update omits them.
284    pub fn update(&mut self, msg: OrderMsg) {
285        self.order_id = msg.order_id;
286        self.order_link_id = msg.order_link_id;
287        self.block_trade_id = msg.block_trade_id;
288        self.symbol = msg.symbol;
289        self.price = msg.price;
290        self.qty = msg.qty;
291        self.side = msg.side;
292        self.is_leverage = msg.is_leverage;
293        self.position_idx = msg.position_idx;
294        self.order_status = msg.order_status;
295        self.create_type = msg.create_type;
296        self.cancel_type = msg.cancel_type;
297        self.reject_reason = msg.reject_reason;
298        self.avg_price = msg.avg_price;
299        if let Some(leaves_qty) = msg.leaves_qty {
300            self.leaves_qty = leaves_qty;
301        }
302        if let Some(leaves_value) = msg.leaves_value {
303            self.leaves_value = leaves_value;
304        }
305        self.cum_exec_qty = msg.cum_exec_qty;
306        self.cum_exec_value = msg.cum_exec_value;
307        self.cum_exec_fee = msg.cum_exec_fee;
308        self.time_in_force = msg.time_in_force;
309        self.order_type = msg.order_type;
310        self.stop_order_type = msg.stop_order_type;
311        self.order_iv = msg.order_iv;
312        self.market_unit = msg.market_unit;
313        self.trigger_price = msg.trigger_price;
314        self.take_profit = msg.take_profit;
315        self.stop_loss = msg.stop_loss;
316        self.tpsl_mode = msg.tpsl_mode;
317        self.oco_trigger_by = msg.oco_trigger_by;
318        self.tp_limit_price = msg.tp_limit_price;
319        self.sl_limit_price = msg.sl_limit_price;
320        self.tp_trigger_by = msg.tp_trigger_by;
321        self.sl_trigger_by = msg.sl_trigger_by;
322        self.trigger_direction = msg.trigger_direction;
323        self.trigger_by = msg.trigger_by;
324        self.last_price_on_created = msg.last_price_on_created;
325        // `base_price` is not sent by the order stream: keep the snapshot value.
326        self.reduce_only = msg.reduce_only;
327        self.close_on_trigger = msg.close_on_trigger;
328        self.place_type = msg.place_type;
329        self.smp_type = msg.smp_type;
330        self.smp_group = msg.smp_group;
331        self.smp_order_id = msg.smp_order_id;
332        self.created_time = msg.created_time;
333        self.updated_time = msg.updated_time;
334    }
335}
336
337/// Build an [`Order`] from its first `order` stream message (an order that
338/// was not in a REST snapshot).
339///
340/// `base_price` is not sent by the stream and stays `None`. When the stream
341/// omits `leavesQty`/`leavesValue`, they are derived as `qty - cumExecQty`
342/// and `price * leavesQty`.
343impl From<OrderMsg> for Order {
344    fn from(msg: OrderMsg) -> Self {
345        let leaves_qty = msg.leaves_qty.unwrap_or(msg.qty - msg.cum_exec_qty);
346        let leaves_value = msg.leaves_value.unwrap_or(msg.price * leaves_qty);
347        Self {
348            order_id: msg.order_id,
349            order_link_id: msg.order_link_id,
350            block_trade_id: msg.block_trade_id,
351            symbol: msg.symbol,
352            price: msg.price,
353            qty: msg.qty,
354            side: msg.side,
355            is_leverage: msg.is_leverage,
356            position_idx: msg.position_idx,
357            order_status: msg.order_status,
358            create_type: msg.create_type,
359            cancel_type: msg.cancel_type,
360            reject_reason: msg.reject_reason,
361            avg_price: msg.avg_price,
362            leaves_qty,
363            leaves_value,
364            cum_exec_qty: msg.cum_exec_qty,
365            cum_exec_value: msg.cum_exec_value,
366            cum_exec_fee: msg.cum_exec_fee,
367            time_in_force: msg.time_in_force,
368            order_type: msg.order_type,
369            stop_order_type: msg.stop_order_type,
370            order_iv: msg.order_iv,
371            market_unit: msg.market_unit,
372            trigger_price: msg.trigger_price,
373            take_profit: msg.take_profit,
374            stop_loss: msg.stop_loss,
375            tpsl_mode: msg.tpsl_mode,
376            oco_trigger_by: msg.oco_trigger_by,
377            tp_limit_price: msg.tp_limit_price,
378            sl_limit_price: msg.sl_limit_price,
379            tp_trigger_by: msg.tp_trigger_by,
380            sl_trigger_by: msg.sl_trigger_by,
381            trigger_direction: msg.trigger_direction,
382            trigger_by: msg.trigger_by,
383            last_price_on_created: msg.last_price_on_created,
384            base_price: None,
385            reduce_only: msg.reduce_only,
386            close_on_trigger: msg.close_on_trigger,
387            place_type: msg.place_type,
388            smp_type: msg.smp_type,
389            smp_group: msg.smp_group,
390            smp_order_id: msg.smp_order_id,
391            created_time: msg.created_time,
392            updated_time: msg.updated_time,
393        }
394    }
395}
396
397/// Request body of `POST /v5/order/create` ([`Client::place_order`](crate::http::Client::place_order)).
398#[derive(Debug, Serialize, Clone)]
399#[serde(rename_all = "camelCase")]
400pub struct PlaceOrderRequest {
401    /// Product type
402    /// UTA2.0, UTA1.0: linear, inverse, spot, option
403    /// classic account: linear, inverse, spot
404    pub category: Category,
405    /// Symbol name, like BTCUSDT, uppercase only
406    pub symbol: String,
407    // Whether to borrow. Unified account Spot trading only.
408    /// 0(default): false, spot trading
409    /// 1: true, margin trading, make sure you turn on margin trading, and set the relevant currency as collateral
410    #[serde(skip_serializing_if = "Option::is_none")]
411    pub is_leverage: Option<i64>,
412    /// Buy, Sell
413    pub side: Side,
414    /// Market, Limit
415    pub order_type: OrderType,
416    /// Order quantity
417    /// UTA account
418    /// Spot: Market Buy order by value by default, you can set marketUnit field to choose order by value or qty for market orders
419    /// Perps, Futures & Option: always order by qty
420    /// classic account
421    /// Spot: Market Buy order by value by default
422    /// Perps, Futures: always order by qty
423    /// Perps & Futures: if you pass qty="0" and specify reduceOnly=true&closeOnTrigger=true, you can close the position up to maxMktOrderQty or maxOrderQty shown on Get Instruments Info of current symbol
424    pub qty: Decimal,
425    /// Select the unit for qty when create Spot market orders for UTA account
426    /// baseCoin: for example, buy BTCUSDT, then "qty" unit is BTC
427    /// quoteCoin: for example, sell BTCUSDT, then "qty" unit is USDT
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub market_unit: Option<String>,
430    /// Slippage tolerance Type for market order, TickSize, Percent
431    /// take profit, stoploss, conditional orders are not supported
432    /// TickSize:
433    /// the highest price of Buy order = ask1 + slippageTolerance x tickSize;
434    /// the lowest price of Sell order = bid1 - slippageTolerance x tickSize
435    /// Percent:
436    /// the highest price of Buy order = ask1 x (1 + slippageTolerance x 0.01);
437    /// the lowest price of Sell order = bid1 x (1 - slippageTolerance x 0.01)
438    #[serde(skip_serializing_if = "Option::is_none")]
439    pub slippage_tolerance_type: Option<Decimal>,
440    /// Slippage tolerance value
441    /// TickSize: range is [1, 10000], integer only
442    /// Percent: range is [0.01, 10], up to 2 decimals
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub slippage_tolerance: Option<Decimal>,
445    /// Order price
446    /// Market order will ignore this field
447    /// Please check the min price and price precision from instrument info endpoint
448    /// If you have position, price needs to be better than liquidation price
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub price: Option<Decimal>,
451    /// Conditional order param. Used to identify the expected direction of the conditional order.
452    /// 1: triggered when market price rises to triggerPrice
453    /// 2: triggered when market price falls to triggerPrice
454    /// Valid for linear & inverse
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub trigger_direction: Option<TriggerDirection>,
457    /// If it is not passed, Order by default.
458    /// Order
459    /// tpslOrder: Spot TP/SL order, the assets are occupied even before the order is triggered
460    /// StopOrder: Spot conditional order, the assets will not be occupied until the price of the underlying asset reaches the trigger price, and the required assets will be occupied after the Conditional order is triggered
461    /// Valid for spot only
462    #[serde(skip_serializing_if = "Option::is_none")]
463    pub order_filter: Option<String>,
464    /// For Perps & Futures, it is the conditional order trigger price. If you expect the price to rise to trigger your conditional order, make sure:
465    /// triggerPrice > market price
466    /// Else, triggerPrice < market price
467    /// For spot, it is the TP/SL and Conditional order trigger price
468    #[serde(skip_serializing_if = "Option::is_none")]
469    pub trigger_price: Option<Decimal>,
470    /// Trigger price type, Conditional order param for Perps & Futures.
471    /// LastPrice
472    /// IndexPrice
473    /// MarkPrice
474    /// Valid for linear & inverse
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub trigger_by: Option<TriggerBy>,
477    /// Implied volatility. option only. Pass the real value, e.g for 10%, 0.1 should be passed. orderIv has a higher priority when price is passed as well
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub order_iv: Option<Decimal>,
480    /// Time in force
481    /// Market order will always use IOC
482    /// If not passed, GTC is used by default
483    #[serde(skip_serializing_if = "Option::is_none")]
484    pub time_in_force: Option<TimeInForce>,
485    /// Used to identify positions in different position modes. Under hedge-mode, this param is required
486    /// 0: one-way mode
487    /// 1: hedge-mode Buy side
488    /// 2: hedge-mode Sell side
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub position_idx: Option<PositionIdx>,
491    /// User customised order ID. A max of 36 characters. Combinations of numbers, letters (upper and lower cases), dashes, and underscores are supported.
492    /// Futures & Perps: orderLinkId rules:
493    /// optional param
494    /// always unique
495    /// option orderLinkId rules:
496    /// required param
497    /// always unique
498    #[serde(skip_serializing_if = "Option::is_none")]
499    pub order_link_id: Option<String>,
500    /// Take profit price
501    /// UTA: Spot Limit order supports take profit, stop loss or limit take profit, limit stop loss when creating an order
502    #[serde(skip_serializing_if = "Option::is_none")]
503    pub take_profit: Option<Decimal>,
504    /// Stop loss price
505    /// UTA: Spot Limit order supports take profit, stop loss or limit take profit, limit stop loss when creating an order
506    #[serde(skip_serializing_if = "Option::is_none")]
507    pub stop_loss: Option<Decimal>,
508    /// The price type to trigger take profit. MarkPrice, IndexPrice, default: LastPrice. Valid for linear & inverse
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub tp_trigger_by: Option<TriggerBy>,
511    /// The price type to trigger stop loss. MarkPrice, IndexPrice, default: LastPrice. Valid for linear & inverse
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub sl_trigger_by: Option<TriggerBy>,
514    /// What is a reduce-only order? true means your position can only reduce in size if this order is triggered.
515    /// You must specify it as true when you are about to close/reduce the position
516    /// When reduceOnly is true, take profit/stop loss cannot be set
517    /// Valid for linear, inverse & option
518    #[serde(skip_serializing_if = "Option::is_none")]
519    pub reduce_only: Option<bool>,
520    /// What is a close on trigger order? For a closing order. It can only reduce your position, not increase it. If the account has insufficient available balance when the closing order is triggered, then other active orders of similar contracts will be cancelled or reduced. It can be used to ensure your stop loss reduces your position regardless of current available margin.
521    /// Valid for linear & inverse
522    #[serde(skip_serializing_if = "Option::is_none")]
523    pub close_on_trigger: Option<bool>,
524    /// Smp execution type. What is SMP?
525    #[serde(skip_serializing_if = "Option::is_none")]
526    pub smp_type: Option<SmpType>,
527    /// Market maker protection. option only. true means set the order as a market maker protection order. What is mmp?
528    #[serde(skip_serializing_if = "Option::is_none")]
529    pub mmp: Option<bool>,
530    /// TP/SL mode
531    /// Full: entire position for TP/SL. Then, tpOrderType or slOrderType must be Market
532    /// Partial: partial position tp/sl (as there is no size option, so it will create tp/sl orders with the qty you actually fill). Limit TP/SL order are supported. Note: When create limit tp/sl, tpslMode is required and it must be Partial
533    /// Valid for linear & inverse
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub tpsl_mode: Option<TpslMode>,
536    /// The limit order price when take profit price is triggered
537    /// linear & inverse: only works when tpslMode=Partial and tpOrderType=Limit
538    /// Spot(UTA): it is required when the order has takeProfit and "tpOrderType"=Limit
539    #[serde(skip_serializing_if = "Option::is_none")]
540    pub tp_limit_price: Option<Decimal>,
541    /// The limit order price when stop loss price is triggered
542    /// linear & inverse: only works when tpslMode=Partial and slOrderType=Limit
543    /// Spot(UTA): it is required when the order has stopLoss and "slOrderType"=Limit
544    #[serde(skip_serializing_if = "Option::is_none")]
545    pub sl_limit_price: Option<Decimal>,
546    /// The order type when take profit is triggered
547    /// linear & inverse: Market(default), Limit. For tpslMode=Full, it only supports tpOrderType=Market
548    /// Spot(UTA):
549    /// Market: when you set "takeProfit",
550    /// Limit: when you set "takeProfit" and "tpLimitPrice"
551    #[serde(skip_serializing_if = "Option::is_none")]
552    pub tp_order_type: Option<OrderType>,
553    /// The order type when stop loss is triggered
554    /// linear & inverse: Market(default), Limit. For tpslMode=Full, it only supports slOrderType=Market
555    /// Spot(UTA):
556    /// Market: when you set "stopLoss",
557    /// Limit: when you set "stopLoss" and "slLimitPrice"
558    #[serde(skip_serializing_if = "Option::is_none")]
559    pub sl_order_type: Option<OrderType>,
560    /// Queue: use the order price on the orderbook in the same direction as the side
561    /// Counterparty: use the order price on the orderbook in the opposite direction as the side
562    /// Valid for linear & inverse
563    #[serde(skip_serializing_if = "Option::is_none")]
564    pub bbo_side_type: Option<String>,
565    /// 1,2,3,4,5 Valid for linear & inverse
566    #[serde(skip_serializing_if = "Option::is_none")]
567    pub bbo_level: Option<String>,
568}
569
570impl PlaceOrderRequest {
571    /// Create with the required fields; set the optional ones with the `with_*` methods.
572    pub fn new(
573        category: Category,
574        symbol: impl Into<String>,
575        side: Side,
576        order_type: OrderType,
577        qty: Decimal,
578    ) -> Self {
579        let symbol: String = symbol.into();
580        Self {
581            category,
582            symbol,
583            is_leverage: None,
584            side,
585            order_type,
586            qty,
587            market_unit: None,
588            slippage_tolerance_type: None,
589            slippage_tolerance: None,
590            price: None,
591            trigger_direction: None,
592            order_filter: None,
593            trigger_price: None,
594            trigger_by: None,
595            order_iv: None,
596            time_in_force: None,
597            position_idx: None,
598            order_link_id: None,
599            take_profit: None,
600            stop_loss: None,
601            tp_trigger_by: None,
602            sl_trigger_by: None,
603            reduce_only: None,
604            close_on_trigger: None,
605            smp_type: None,
606            mmp: None,
607            tpsl_mode: None,
608            tp_limit_price: None,
609            sl_limit_price: None,
610            tp_order_type: None,
611            sl_order_type: None,
612            bbo_side_type: None,
613            bbo_level: None,
614        }
615    }
616
617    /// Set `is_leverage`: 0(default): false, spot trading 1: true, margin trading, make sure you turn on margin trading, and set the relevant currency as collateral.
618    pub fn with_is_leverage(mut self, v: i64) -> Self {
619        self.is_leverage = Some(v);
620        self
621    }
622    /// Set `market_unit`: select the unit for qty when create Spot market orders for UTA account baseCoin: for example, buy BTCUSDT, then "qty" unit is BTC quoteCoin: for example, sell BTCUSDT, then "qty" unit is USDT.
623    pub fn with_market_unit(mut self, v: impl Into<String>) -> Self {
624        self.market_unit = Some(v.into());
625        self
626    }
627    /// Set `slippage_tolerance_type`: slippage tolerance Type for market order, TickSize, Percent take profit, stoploss, conditional orders are not supported TickSize: the highest price of Buy order = ask1 + slippageTolerance x tickSize;.
628    pub fn with_slippage_tolerance_type(mut self, v: Decimal) -> Self {
629        self.slippage_tolerance_type = Some(v);
630        self
631    }
632    /// Set `slippage_tolerance`: slippage tolerance value TickSize: range is [1, 10000], integer only Percent: range is [0.01, 10], up to 2 decimals.
633    pub fn with_slippage_tolerance(mut self, v: Decimal) -> Self {
634        self.slippage_tolerance = Some(v);
635        self
636    }
637    /// Set `price`: order price Market order will ignore this field Please check the min price and price precision from instrument info endpoint If you have position, price needs to be better than liquidation price.
638    pub fn with_price(mut self, v: Decimal) -> Self {
639        self.price = Some(v);
640        self
641    }
642    /// Set `trigger_direction`: conditional order param. Used to identify the expected direction of the conditional order. 1: triggered when market price rises to triggerPrice 2: triggered when market price falls to triggerPrice Valid for linear & inverse.
643    pub fn with_trigger_direction(mut self, v: TriggerDirection) -> Self {
644        self.trigger_direction = Some(v);
645        self
646    }
647    /// Set `order_filter`: if it is not passed, Order by default. Order tpslOrder: Spot TP/SL order, the assets are occupied even before the order is triggered StopOrder: Spot conditional order, the assets will not be occupied until the price of the underlying asset ...
648    pub fn with_order_filter(mut self, v: impl Into<String>) -> Self {
649        self.order_filter = Some(v.into());
650        self
651    }
652    /// Set `trigger_price`: for Perps & Futures, it is the conditional order trigger price.
653    pub fn with_trigger_price(mut self, v: Decimal) -> Self {
654        self.trigger_price = Some(v);
655        self
656    }
657    /// Set `trigger_by`: trigger price type, Conditional order param for Perps & Futures. LastPrice IndexPrice MarkPrice Valid for linear & inverse.
658    pub fn with_trigger_by(mut self, v: TriggerBy) -> Self {
659        self.trigger_by = Some(v);
660        self
661    }
662    /// Set `order_iv`: implied volatility. option only. Pass the real value, e.g for 10%, 0.1 should be passed. orderIv has a higher priority when price is passed as well.
663    pub fn with_order_iv(mut self, v: Decimal) -> Self {
664        self.order_iv = Some(v);
665        self
666    }
667    /// Set `time_in_force`: time in force Market order will always use IOC If not passed, GTC is used by default.
668    pub fn with_time_in_force(mut self, v: TimeInForce) -> Self {
669        self.time_in_force = Some(v);
670        self
671    }
672    /// Set `position_idx`: used to identify positions in different position modes. Under hedge-mode, this param is required 0: one-way mode 1: hedge-mode Buy side 2: hedge-mode Sell side.
673    pub fn with_position_idx(mut self, v: PositionIdx) -> Self {
674        self.position_idx = Some(v);
675        self
676    }
677    /// Set `order_link_id`: user customised order ID. A max of 36 characters. Combinations of numbers, letters (upper and lower cases), dashes, and underscores are supported.
678    pub fn with_order_link_id(mut self, v: impl Into<String>) -> Self {
679        self.order_link_id = Some(v.into());
680        self
681    }
682    /// Set `take_profit`: take profit price UTA: Spot Limit order supports take profit, stop loss or limit take profit, limit stop loss when creating an order.
683    pub fn with_take_profit(mut self, v: Decimal) -> Self {
684        self.take_profit = Some(v);
685        self
686    }
687    /// Set `stop_loss`: stop loss price UTA: Spot Limit order supports take profit, stop loss or limit take profit, limit stop loss when creating an order.
688    pub fn with_stop_loss(mut self, v: Decimal) -> Self {
689        self.stop_loss = Some(v);
690        self
691    }
692    /// Set `tp_trigger_by`: the price type to trigger take profit. MarkPrice, IndexPrice, default: LastPrice. Valid for linear & inverse.
693    pub fn with_tp_trigger_by(mut self, v: TriggerBy) -> Self {
694        self.tp_trigger_by = Some(v);
695        self
696    }
697    /// Set `sl_trigger_by`: the price type to trigger stop loss. MarkPrice, IndexPrice, default: LastPrice. Valid for linear & inverse.
698    pub fn with_sl_trigger_by(mut self, v: TriggerBy) -> Self {
699        self.sl_trigger_by = Some(v);
700        self
701    }
702    /// Set `reduce_only`: what is a reduce-only order? true means your position can only reduce in size if this order is triggered.
703    pub fn with_reduce_only(mut self, v: bool) -> Self {
704        self.reduce_only = Some(v);
705        self
706    }
707    /// Set `close_on_trigger`: what is a close on trigger order? For a closing order. It can only reduce your position, not increase it.
708    pub fn with_close_on_trigger(mut self, v: bool) -> Self {
709        self.close_on_trigger = Some(v);
710        self
711    }
712    /// Set `smp_type`: smp execution type. What is SMP?
713    pub fn with_smp_type(mut self, v: SmpType) -> Self {
714        self.smp_type = Some(v);
715        self
716    }
717    /// Set `mmp`: market maker protection. option only. true means set the order as a market maker protection order. What is mmp?
718    pub fn with_mmp(mut self, v: bool) -> Self {
719        self.mmp = Some(v);
720        self
721    }
722    /// Set `tpsl_mode`: tP/SL mode Full: entire position for TP/SL. Then, tpOrderType or slOrderType must be Market Partial: partial position tp/sl (as there is no size option, so it will create tp/sl orders with the qty you actually fill).
723    pub fn with_tpsl_mode(mut self, v: TpslMode) -> Self {
724        self.tpsl_mode = Some(v);
725        self
726    }
727    /// Set `tp_limit_price`: the limit order price when take profit price is triggered linear & inverse: only works when tpslMode=Partial and tpOrderType=Limit Spot(UTA): it is required when the order has takeProfit and "tpOrderType"=Limit.
728    pub fn with_tp_limit_price(mut self, v: Decimal) -> Self {
729        self.tp_limit_price = Some(v);
730        self
731    }
732    /// Set `sl_limit_price`: the limit order price when stop loss price is triggered linear & inverse: only works when tpslMode=Partial and slOrderType=Limit Spot(UTA): it is required when the order has stopLoss and "slOrderType"=Limit.
733    pub fn with_sl_limit_price(mut self, v: Decimal) -> Self {
734        self.sl_limit_price = Some(v);
735        self
736    }
737    /// Set `tp_order_type`: the order type when take profit is triggered linear & inverse: Market(default), Limit. For tpslMode=Full, it only supports tpOrderType=Market Spot(UTA): Market: when you set "takeProfit", Limit: when you set "takeProfit" and "tpLimitPrice".
738    pub fn with_tp_order_type(mut self, v: OrderType) -> Self {
739        self.tp_order_type = Some(v);
740        self
741    }
742    /// Set `sl_order_type`: the order type when stop loss is triggered linear & inverse: Market(default), Limit. For tpslMode=Full, it only supports slOrderType=Market Spot(UTA): Market: when you set "stopLoss", Limit: when you set "stopLoss" and "slLimitPrice".
743    pub fn with_sl_order_type(mut self, v: OrderType) -> Self {
744        self.sl_order_type = Some(v);
745        self
746    }
747    /// Set `bbo_side_type`: queue: use the order price on the orderbook in the same direction as the side Counterparty: use the order price on the orderbook in the opposite direction as the side Valid for linear & inverse.
748    pub fn with_bbo_side_type(mut self, v: impl Into<String>) -> Self {
749        self.bbo_side_type = Some(v.into());
750        self
751    }
752    /// Set `bbo_level`: 1,2,3,4,5 Valid for linear & inverse.
753    pub fn with_bbo_level(mut self, v: impl Into<String>) -> Self {
754        self.bbo_level = Some(v.into());
755        self
756    }
757}
758
759/// Result of `POST /v5/order/create` ([`Client::place_order`](crate::http::Client::place_order)).
760#[derive(Debug, Deserialize, PartialEq)]
761#[serde(rename_all = "camelCase")]
762pub struct PlaceOrderResponse {
763    /// Order ID
764    pub order_id: String,
765    /// User customised order ID
766    pub order_link_id: String,
767}
768
769/// Request body of `POST /v5/order/amend` ([`Client::amend_order`](crate::http::Client::amend_order)).
770#[derive(Debug, Serialize, Clone)]
771#[serde(rename_all = "camelCase")]
772pub struct AmendOrderRequest {
773    /// Product type linear, inverse, spot, option.
774    pub category: Category,
775    /// Symbol name, like BTCUSDT, uppercase only.
776    pub symbol: String,
777    /// Order ID. Either orderId or orderLinkId is required.
778    #[serde(skip_serializing_if = "Option::is_none")]
779    pub order_id: Option<String>,
780    /// User customised order ID. Either orderId or orderLinkId is required.
781    #[serde(skip_serializing_if = "Option::is_none")]
782    pub order_link_id: Option<String>,
783    /// Order quantity after modification. Do not pass it if not modify the qty.
784    #[serde(skip_serializing_if = "Option::is_none")]
785    pub qty: Option<Decimal>,
786    /// Order price after modification. Do not pass it if not modify the price.
787    #[serde(skip_serializing_if = "Option::is_none")]
788    pub price: Option<Decimal>,
789}
790
791impl AmendOrderRequest {
792    /// Create with the required fields; set the optional ones with the `with_*` methods.
793    pub fn new(category: Category, symbol: impl Into<String>) -> Self {
794        let symbol: String = symbol.into();
795        Self {
796            category,
797            symbol,
798            order_id: None,
799            order_link_id: None,
800            qty: None,
801            price: None,
802        }
803    }
804
805    /// Set `order_id`: order ID. Either orderId or orderLinkId is required.
806    pub fn with_order_id(mut self, v: impl Into<String>) -> Self {
807        self.order_id = Some(v.into());
808        self
809    }
810    /// Set `order_link_id`: user customised order ID. Either orderId or orderLinkId is required.
811    pub fn with_order_link_id(mut self, v: impl Into<String>) -> Self {
812        self.order_link_id = Some(v.into());
813        self
814    }
815    /// Set `qty`: order quantity after modification. Do not pass it if not modify the qty.
816    pub fn with_qty(mut self, v: Decimal) -> Self {
817        self.qty = Some(v);
818        self
819    }
820    /// Set `price`: order price after modification. Do not pass it if not modify the price.
821    pub fn with_price(mut self, v: Decimal) -> Self {
822        self.price = Some(v);
823        self
824    }
825}
826
827/// Result of `POST /v5/order/amend` ([`Client::amend_order`](crate::http::Client::amend_order)).
828#[derive(Debug, Deserialize, PartialEq)]
829#[serde(rename_all = "camelCase")]
830pub struct AmendOrderResponse {
831    /// Order ID
832    pub order_id: String,
833    #[serde(default, deserialize_with = "empty_string_as_none")]
834    /// User customised order ID
835    pub order_link_id: Option<String>,
836}
837
838/// Request body of `POST /v5/order/cancel` ([`Client::cancel_order`](crate::http::Client::cancel_order)).
839#[derive(Debug, Serialize, Clone)]
840#[serde(rename_all = "camelCase")]
841pub struct CancelOrderRequest {
842    /// Product type. linear, inverse, spot, option
843    pub category: Category,
844    /// Symbol name, like BTCUSDT, uppercase only
845    pub symbol: String,
846    /// Order ID. Either orderId or orderLinkId is required
847    #[serde(skip_serializing_if = "Option::is_none")]
848    pub order_id: Option<String>,
849    /// User customised order ID. Either orderId or orderLinkId is required
850    #[serde(skip_serializing_if = "Option::is_none")]
851    pub order_link_id: Option<String>,
852    /// Spot trading only
853    /// Order, tpslOrder, StopOrder
854    /// If not passed, Order by default
855    #[serde(skip_serializing_if = "Option::is_none")]
856    pub order_filter: Option<OrderFilter>,
857}
858
859impl CancelOrderRequest {
860    /// Create with the required fields; set the optional ones with the `with_*` methods.
861    pub fn new(category: Category, symbol: impl Into<String>) -> Self {
862        let symbol: String = symbol.into();
863        Self {
864            category,
865            symbol,
866            order_id: None,
867            order_link_id: None,
868            order_filter: None,
869        }
870    }
871
872    /// Set `order_id`: order ID. Either orderId or orderLinkId is required.
873    pub fn with_order_id(mut self, v: impl Into<String>) -> Self {
874        self.order_id = Some(v.into());
875        self
876    }
877    /// Set `order_link_id`: user customised order ID. Either orderId or orderLinkId is required.
878    pub fn with_order_link_id(mut self, v: impl Into<String>) -> Self {
879        self.order_link_id = Some(v.into());
880        self
881    }
882    /// Set `order_filter`: spot trading only Order, tpslOrder, StopOrder If not passed, Order by default.
883    pub fn with_order_filter(mut self, v: OrderFilter) -> Self {
884        self.order_filter = Some(v);
885        self
886    }
887}
888
889/// Result of `POST /v5/order/cancel` ([`Client::cancel_order`](crate::http::Client::cancel_order)).
890#[derive(Debug, Deserialize, PartialEq)]
891#[serde(rename_all = "camelCase")]
892pub struct CancelOrderResponse {
893    /// Order ID
894    pub order_id: String,
895    /// User customised order ID
896    #[serde(default, deserialize_with = "empty_string_as_none")]
897    pub order_link_id: Option<String>,
898}
899
900// ── Cancel All Orders ────────────────────────────────────────────────────────
901
902/// Request body of `POST /v5/order/cancel-all` ([`Client::cancel_all_orders`](crate::http::Client::cancel_all_orders)).
903#[derive(Debug, Serialize, Clone)]
904#[serde(rename_all = "camelCase")]
905pub struct CancelAllOrdersRequest {
906    /// Product type. linear, inverse, spot, option.
907    pub category: Category,
908    /// Symbol name, like BTCUSDT, uppercase only linear & inverse: Required if not passing baseCoin or settleCoin.
909    #[serde(skip_serializing_if = "Option::is_none")]
910    pub symbol: Option<String>,
911    /// Base coin, uppercase only. linear & inverse: If cancel all by baseCoin, it will cancel all of the corresponding category's orders. Required if not passing symbol or settleCoin.
912    #[serde(skip_serializing_if = "Option::is_none")]
913    pub base_coin: Option<String>,
914    /// Settle coin, uppercase only linear & inverse: Required if not passing symbol or baseCoin option: USDT or USDC Not support spot.
915    #[serde(skip_serializing_if = "Option::is_none")]
916    pub settle_coin: Option<String>,
917    /// Category= spot, you can pass Order, tpslOrder, StopOrder, OcoOrder, BidirectionalTpslOrder If not passed, Order by default category= linear or inverse, you can pass Order, StopOrder, OpenOrder If not passed, all kinds of orders will be ...
918    #[serde(skip_serializing_if = "Option::is_none")]
919    pub order_filter: Option<OrderFilter>,
920    /// For futures only: TakeProfit, StopLoss, TrailingStop
921    #[serde(skip_serializing_if = "Option::is_none")]
922    pub stop_order_type: Option<StopOrderType>,
923}
924
925impl CancelAllOrdersRequest {
926    /// Create with the required fields; set the optional ones with the `with_*` methods.
927    pub fn new(category: Category) -> Self {
928        Self {
929            category,
930            symbol: None,
931            base_coin: None,
932            settle_coin: None,
933            order_filter: None,
934            stop_order_type: None,
935        }
936    }
937
938    /// Set `symbol`: symbol name, like BTCUSDT, uppercase only linear & inverse: Required if not passing baseCoin or settleCoin.
939    pub fn with_symbol(mut self, v: impl Into<String>) -> Self {
940        self.symbol = Some(v.into());
941        self
942    }
943    /// Set `base_coin`: base coin, uppercase only. linear & inverse: If cancel all by baseCoin, it will cancel all of the corresponding category's orders. Required if not passing symbol or settleCoin.
944    pub fn with_base_coin(mut self, v: impl Into<String>) -> Self {
945        self.base_coin = Some(v.into());
946        self
947    }
948    /// Set `settle_coin`: settle coin, uppercase only linear & inverse: Required if not passing symbol or baseCoin option: USDT or USDC Not support spot.
949    pub fn with_settle_coin(mut self, v: impl Into<String>) -> Self {
950        self.settle_coin = Some(v.into());
951        self
952    }
953    /// Set `order_filter`: category= spot, you can pass Order, tpslOrder, StopOrder, OcoOrder, BidirectionalTpslOrder If not passed, Order by default category= linear or inverse, you can pass Order, StopOrder, OpenOrder If not passed, all kinds of orders will be ...
954    pub fn with_order_filter(mut self, v: OrderFilter) -> Self {
955        self.order_filter = Some(v);
956        self
957    }
958    /// Set `stop_order_type`: for futures only: TakeProfit, StopLoss, TrailingStop.
959    pub fn with_stop_order_type(mut self, v: StopOrderType) -> Self {
960        self.stop_order_type = Some(v);
961        self
962    }
963}
964
965/// Response for cancel-all: a flat list of cancelled order IDs.
966pub type CancelAllOrdersResponse = List<CancelOrderResponse>;
967
968// ── Order History ────────────────────────────────────────────────────────────
969
970/// Query parameters of `GET /v5/order/history` ([`Client::get_order_history`](crate::http::Client::get_order_history)).
971#[derive(Clone, Debug, Serialize)]
972#[serde(rename_all = "camelCase")]
973pub struct GetOrderHistoryParams {
974    /// Product type linear, inverse, spot, option.
975    pub category: Category,
976    /// Symbol name, like BTCUSDT, uppercase only.
977    #[serde(skip_serializing_if = "Option::is_none")]
978    pub symbol: Option<String>,
979    /// Base coin, uppercase only.
980    #[serde(skip_serializing_if = "Option::is_none")]
981    pub base_coin: Option<String>,
982    /// Settle coin, uppercase only.
983    #[serde(skip_serializing_if = "Option::is_none")]
984    pub settle_coin: Option<String>,
985    /// Order ID.
986    #[serde(skip_serializing_if = "Option::is_none")]
987    pub order_id: Option<String>,
988    /// User customised order ID.
989    #[serde(skip_serializing_if = "Option::is_none")]
990    pub order_link_id: Option<String>,
991    /// Order: active order StopOrder: conditional order for Futures and Spot tpslOrder: spot TP/SL order OcoOrder: spot OCO orders BidirectionalTpslOrder: Spot bidirectional TPSL order all kinds of orders are returned by default.
992    #[serde(skip_serializing_if = "Option::is_none")]
993    pub order_filter: Option<OrderFilter>,
994    /// Order status.
995    #[serde(skip_serializing_if = "Option::is_none")]
996    pub order_status: Option<OrderStatus>,
997    /// Start timestamp (ms)
998    #[serde(skip_serializing_if = "Option::is_none")]
999    pub start_time: Option<Timestamp>,
1000    /// End timestamp (ms)
1001    #[serde(skip_serializing_if = "Option::is_none")]
1002    pub end_time: Option<Timestamp>,
1003    /// [1, 50]. Default: 20
1004    #[serde(skip_serializing_if = "Option::is_none")]
1005    pub limit: Option<i32>,
1006    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1007    #[serde(skip_serializing_if = "Option::is_none")]
1008    pub cursor: Option<String>,
1009}
1010
1011impl GetOrderHistoryParams {
1012    /// Create with the required fields; set the optional ones with the `with_*` methods.
1013    pub fn new(category: Category) -> Self {
1014        Self {
1015            category,
1016            symbol: None,
1017            base_coin: None,
1018            settle_coin: None,
1019            order_id: None,
1020            order_link_id: None,
1021            order_filter: None,
1022            order_status: None,
1023            start_time: None,
1024            end_time: None,
1025            limit: None,
1026            cursor: None,
1027        }
1028    }
1029
1030    /// Set `symbol`: symbol name, like BTCUSDT, uppercase only.
1031    pub fn with_symbol(mut self, v: impl Into<String>) -> Self {
1032        self.symbol = Some(v.into());
1033        self
1034    }
1035    /// Set `base_coin`: base coin, uppercase only.
1036    pub fn with_base_coin(mut self, v: impl Into<String>) -> Self {
1037        self.base_coin = Some(v.into());
1038        self
1039    }
1040    /// Set `settle_coin`: settle coin, uppercase only.
1041    pub fn with_settle_coin(mut self, v: impl Into<String>) -> Self {
1042        self.settle_coin = Some(v.into());
1043        self
1044    }
1045    /// Set `order_id`: order ID.
1046    pub fn with_order_id(mut self, v: impl Into<String>) -> Self {
1047        self.order_id = Some(v.into());
1048        self
1049    }
1050    /// Set `order_link_id`: user customised order ID.
1051    pub fn with_order_link_id(mut self, v: impl Into<String>) -> Self {
1052        self.order_link_id = Some(v.into());
1053        self
1054    }
1055    /// Set `order_filter`: order: active order StopOrder: conditional order for Futures and Spot tpslOrder: spot TP/SL order OcoOrder: spot OCO orders BidirectionalTpslOrder: Spot bidirectional TPSL order all kinds of orders are returned by default.
1056    pub fn with_order_filter(mut self, v: OrderFilter) -> Self {
1057        self.order_filter = Some(v);
1058        self
1059    }
1060    /// Set `order_status`: order status.
1061    pub fn with_order_status(mut self, v: OrderStatus) -> Self {
1062        self.order_status = Some(v);
1063        self
1064    }
1065    /// Set `start_time`: start timestamp (ms).
1066    pub fn with_start_time(mut self, v: Timestamp) -> Self {
1067        self.start_time = Some(v);
1068        self
1069    }
1070    /// Set `end_time`: end timestamp (ms).
1071    pub fn with_end_time(mut self, v: Timestamp) -> Self {
1072        self.end_time = Some(v);
1073        self
1074    }
1075    /// Set `limit`: [1, 50]. Default: 20.
1076    pub fn with_limit(mut self, v: i32) -> Self {
1077        self.limit = Some(v);
1078        self
1079    }
1080    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1081    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
1082        self.cursor = Some(v.into());
1083        self
1084    }
1085}
1086
1087// ── Batch Orders ─────────────────────────────────────────────────────────────
1088
1089/// Request body of `POST /v5/order/create-batch` ([`Client::place_orders_batch`](crate::http::Client::place_orders_batch)).
1090#[derive(Debug, Serialize, Clone)]
1091#[serde(rename_all = "camelCase")]
1092pub struct PlaceOrderBatchRequest {
1093    /// Product type linear, option, spot, inverse.
1094    pub category: Category,
1095    /// Orders to create (at most 20 for linear, 10 otherwise).
1096    pub request: Vec<PlaceOrderRequest>,
1097}
1098
1099impl PlaceOrderBatchRequest {
1100    /// Create with the required fields; set the optional ones with the `with_*` methods.
1101    pub fn new(category: Category, request: Vec<PlaceOrderRequest>) -> Self {
1102        Self { category, request }
1103    }
1104}
1105
1106/// Request body of `POST /v5/order/amend-batch` ([`Client::amend_orders_batch`](crate::http::Client::amend_orders_batch)).
1107#[derive(Debug, Serialize, Clone)]
1108#[serde(rename_all = "camelCase")]
1109pub struct AmendOrderBatchRequest {
1110    /// Product type linear, option, spot, inverse.
1111    pub category: Category,
1112    /// Orders to amend.
1113    pub request: Vec<AmendOrderRequest>,
1114}
1115
1116impl AmendOrderBatchRequest {
1117    /// Create with the required fields; set the optional ones with the `with_*` methods.
1118    pub fn new(category: Category, request: Vec<AmendOrderRequest>) -> Self {
1119        Self { category, request }
1120    }
1121}
1122
1123/// Request body of `POST /v5/order/cancel-batch` ([`Client::cancel_orders_batch`](crate::http::Client::cancel_orders_batch)).
1124#[derive(Debug, Serialize, Clone)]
1125#[serde(rename_all = "camelCase")]
1126pub struct CancelOrderBatchRequest {
1127    /// Product type linear, option, spot, inverse.
1128    pub category: Category,
1129    /// Orders to cancel.
1130    pub request: Vec<CancelOrderRequest>,
1131}
1132
1133impl CancelOrderBatchRequest {
1134    /// Create with the required fields; set the optional ones with the `with_*` methods.
1135    pub fn new(category: Category, request: Vec<CancelOrderRequest>) -> Self {
1136        Self { category, request }
1137    }
1138}
1139
1140/// Item of the list returned by `POST /v5/order/create-batch` ([`Client::place_orders_batch`](crate::http::Client::place_orders_batch)).
1141#[derive(Debug, Deserialize, PartialEq)]
1142#[serde(rename_all = "camelCase")]
1143pub struct PlaceOrderBatchResult {
1144    /// Product type.
1145    pub category: Category,
1146    /// Symbol name.
1147    pub symbol: String,
1148    /// Order ID.
1149    pub order_id: String,
1150    /// User customised order ID.
1151    #[serde(default, deserialize_with = "empty_string_as_none")]
1152    pub order_link_id: Option<String>,
1153    /// Order creation timestamp (ms). Note: Bybit uses the non-standard key "createAt".
1154    #[serde(rename = "createAt", default)]
1155    pub create_at: Option<Timestamp>,
1156}
1157
1158/// Item of the list returned by `POST /v5/order/amend-batch` ([`Client::amend_orders_batch`](crate::http::Client::amend_orders_batch)).
1159#[derive(Debug, Deserialize, PartialEq)]
1160#[serde(rename_all = "camelCase")]
1161pub struct AmendOrderBatchResult {
1162    /// Product type.
1163    pub category: Category,
1164    /// Symbol name.
1165    pub symbol: String,
1166    /// Order ID.
1167    pub order_id: String,
1168    /// User customised order ID.
1169    #[serde(default, deserialize_with = "empty_string_as_none")]
1170    pub order_link_id: Option<String>,
1171}
1172
1173/// Item of the list returned by `POST /v5/order/cancel-batch` ([`Client::cancel_orders_batch`](crate::http::Client::cancel_orders_batch)).
1174#[derive(Debug, Deserialize, PartialEq)]
1175#[serde(rename_all = "camelCase")]
1176pub struct CancelOrderBatchResult {
1177    /// Product type.
1178    pub category: Category,
1179    /// Symbol name.
1180    pub symbol: String,
1181    /// Order ID.
1182    pub order_id: String,
1183    /// User customised order ID.
1184    #[serde(default, deserialize_with = "empty_string_as_none")]
1185    pub order_link_id: Option<String>,
1186}
1187
1188// --- Spot Borrow Check ---
1189
1190/// Query params for [`Client::get_spot_borrow_check`](crate::http::Client::get_spot_borrow_check).
1191///
1192/// Covers: spot only
1193#[derive(Debug, Serialize, Clone)]
1194pub struct GetSpotBorrowCheckParams {
1195    /// Product type spot.
1196    pub category: Category,
1197    /// Symbol name.
1198    pub symbol: String,
1199    /// Transaction side. Buy, Sell.
1200    pub side: Side,
1201}
1202
1203impl GetSpotBorrowCheckParams {
1204    /// Create with the required fields; set the optional ones with the `with_*` methods.
1205    pub fn new(category: Category, symbol: impl Into<String>, side: Side) -> Self {
1206        Self {
1207            category,
1208            symbol: symbol.into(),
1209            side,
1210        }
1211    }
1212}
1213
1214/// Response for [`Client::get_spot_borrow_check`](crate::http::Client::get_spot_borrow_check).
1215#[derive(Debug, Deserialize, PartialEq)]
1216#[serde(rename_all = "camelCase")]
1217pub struct SpotBorrowCheck {
1218    /// Symbol name, like BTCUSDT, uppercase only.
1219    pub symbol: String,
1220    /// Side.
1221    pub side: Side,
1222    /// Maximum tradeable quantity with leverage (in base coin).
1223    pub max_trade_qty: Decimal,
1224    /// Maximum tradeable amount with leverage (in quote coin).
1225    pub max_trade_amount: Decimal,
1226    /// Maximum tradeable quantity without leverage (in base coin).
1227    pub spot_max_trade_qty: Decimal,
1228    /// Maximum tradeable amount without leverage (in quote coin).
1229    pub spot_max_trade_amount: Decimal,
1230    /// The coin to borrow.
1231    pub borrow_coin: String,
1232}