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#[derive(Clone, Debug, Serialize)]
15#[serde(rename_all = "camelCase")]
16pub struct GetOpenClosedOrdersParams {
17    /// Product type
18    /// UTA2.0, UTA1.0: linear, inverse, spot, option
19    /// classic account: linear, inverse, spot
20    pub category: Category,
21    /// Symbol name, like BTCUSDT, uppercase only. For linear, either symbol, baseCoin, settleCoin is required
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub symbol: Option<String>,
24    /// Base coin, uppercase only
25    /// Supports linear, inverse & option
26    /// option: it returns all option open orders by default
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub base_coin: Option<String>,
29    /// Settle coin, uppercase only
30    /// linear: either symbol, baseCoin or settleCoin is required
31    /// spot: not supported
32    /// option: USDT or USDC
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub settle_coin: Option<String>,
35    /// Order ID
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub order_id: Option<String>,
38    /// User customized order ID
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub order_link_id: Option<String>,
41    /// 0(default): UTA2.0, UTA1.0, classic account query open status orders (e.g., New, PartiallyFilled) only
42    /// 1: UTA2.0, UTA1.0(except inverse)
43    /// 2: UTA1.0(inverse), classic account
44    /// Query a maximum of recent 500 closed status records are kept under each account each category (e.g., Cancelled, Rejected, Filled orders).
45    /// 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
46    /// openOnly param will be ignored when query by orderId or orderLinkId
47    /// Classic spot: not supported
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub open_only: Option<i32>,
50    /// Order: active order,
51    /// StopOrder: conditional order for Futures and Spot,
52    /// tpslOrder: spot TP/SL order,
53    /// OcoOrder: Spot oco order,
54    /// BidirectionalTpslOrder: Spot bidirectional TPSL order
55    /// - classic account spot: return Order active order by default
56    /// - Others: all kinds of orders by default
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub order_filter: Option<OrderFilter>,
59    /// Limit for data size per page. [1, 50]. Default: 20
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub limit: Option<i32>,
62    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub cursor: Option<String>,
65}
66
67impl GetOpenClosedOrdersParams {
68    pub fn new(category: Category) -> Self {
69        Self {
70            category,
71            symbol: None,
72            base_coin: None,
73            settle_coin: None,
74            order_id: None,
75            order_link_id: None,
76            open_only: None,
77            order_filter: None,
78            limit: None,
79            cursor: None,
80        }
81    }
82
83    pub fn with_symbol(mut self, v: String) -> Self {
84        self.symbol = Some(v);
85        self
86    }
87    pub fn with_base_coin(mut self, v: String) -> Self {
88        self.base_coin = Some(v);
89        self
90    }
91    pub fn with_settle_coin(mut self, v: String) -> Self {
92        self.settle_coin = Some(v);
93        self
94    }
95    pub fn with_order_id(mut self, v: String) -> Self {
96        self.order_id = Some(v);
97        self
98    }
99    pub fn with_order_link_id(mut self, v: String) -> Self {
100        self.order_link_id = Some(v);
101        self
102    }
103    pub fn with_open_only(mut self, v: i32) -> Self {
104        self.open_only = Some(v);
105        self
106    }
107    pub fn with_order_filter(mut self, v: OrderFilter) -> Self {
108        self.order_filter = Some(v);
109        self
110    }
111    pub fn with_limit(mut self, v: i32) -> Self {
112        self.limit = Some(v);
113        self
114    }
115    pub fn with_cursor(mut self, v: String) -> Self {
116        self.cursor = Some(v);
117        self
118    }
119}
120
121#[derive(Debug, Clone, Serialize)]
122pub enum OrderFilter {
123    /// active order,
124    Order,
125    /// conditional order for Futures and Spot,
126    StopOrder,
127    /// spot TP/SL order,
128    #[serde(rename = "camelCase")]
129    TpslOrder,
130    /// Spot oco order,
131    OcoOrder,
132    /// Spot bidirectional TPSL order
133    /// - classic account spot: return Order active order by default
134    /// - Others: all kinds of orders by default
135    BidirectionalTpslOrder,
136}
137
138#[derive(Debug, Deserialize, PartialEq, Clone)]
139#[serde(rename_all = "camelCase")]
140pub struct Order {
141    /// Order ID
142    pub order_id: String,
143    /// User customized order ID
144    #[serde(default, deserialize_with = "empty_string_as_none")]
145    pub order_link_id: Option<String>,
146    /// Paradigm block trade ID
147    #[serde(default, deserialize_with = "empty_string_as_none")]
148    pub block_trade_id: Option<String>,
149    /// Symbol name
150    pub symbol: String,
151    /// Order price
152    pub price: Decimal,
153    /// Order qty
154    pub qty: Decimal,
155    /// Side. Buy,Sell
156    pub side: Side,
157    /// Whether to borrow. Unified spot only. 0: false, 1: true. Classic spot is not supported, always 0
158    #[serde(default, deserialize_with = "string_to_option_bool")]
159    pub is_leverage: Option<bool>,
160    /// Position index. Used to identify positions in different position modes.
161    pub position_idx: PositionIdx,
162    /// Order status
163    pub order_status: OrderStatus,
164    /// Order create type
165    /// Only for category=linear or inverse
166    /// Spot, Option do not have this key
167    #[serde(default, deserialize_with = "empty_string_as_none")]
168    pub create_type: Option<CreateType>,
169    /// Cancel type
170    pub cancel_type: CancelType,
171    /// Reject reason. Classic spot is not supported
172    pub reject_reason: RejectReason,
173    /// Average filled price
174    /// UTA: returns "" for those orders without avg price
175    /// classic account: returns "0" for those orders without avg price, and also for those orders have partially filled but cancelled at the end
176    #[serde(default, deserialize_with = "option_decimal")]
177    pub avg_price: Option<Decimal>,
178    /// The remaining qty not executed. Classic spot is not supported
179    pub leaves_qty: Decimal,
180    /// The estimated value not executed. Classic spot is not supported
181    pub leaves_value: Decimal,
182    /// Cumulative executed order qty
183    pub cum_exec_qty: Decimal,
184    /// Cumulative executed order value. Classic spot is not supported
185    pub cum_exec_value: Decimal,
186    /// Cumulative executed trading fee. Classic spot is not supported
187    pub cum_exec_fee: Decimal,
188    /// Time in force
189    pub time_in_force: TimeInForce,
190    /// Order type. Market,Limit. For TP/SL order, it means the order type after triggered
191    pub order_type: OrderType,
192    /// Stop order type
193    #[serde(default, deserialize_with = "empty_string_as_none")]
194    pub stop_order_type: Option<StopOrderType>,
195    /// Implied volatility
196    #[serde(default, deserialize_with = "option_decimal")]
197    pub order_iv: Option<Decimal>,
198    /// The unit for qty when create Spot market orders for UTA account. baseCoin, quoteCoin
199    #[serde(default, deserialize_with = "empty_string_as_none")]
200    pub market_unit: Option<String>,
201    /// Trigger price. If stopOrderType=TrailingStop, it is activate price. Otherwise, it is trigger price
202    #[serde(default, deserialize_with = "option_decimal")]
203    pub trigger_price: Option<Decimal>,
204    /// Take profit price
205    #[serde(default, deserialize_with = "option_decimal")]
206    pub take_profit: Option<Decimal>,
207    /// Stop loss price
208    #[serde(default, deserialize_with = "option_decimal")]
209    pub stop_loss: Option<Decimal>,
210    /// TP/SL mode, Full: entire position for TP/SL. Partial: partial position tp/sl. Spot does not have this field, and Option returns always ""
211    #[serde(default, deserialize_with = "empty_string_as_none")]
212    pub tpsl_mode: Option<TpslMode>,
213    /// The trigger type of Spot OCO order.OcoTriggerByUnknown, OcoTriggerByTp, OcoTriggerByBySl. Classic spot is not supported
214    #[serde(default, deserialize_with = "empty_string_as_none")]
215    pub oco_trigger_by: Option<OcoTriggerBy>,
216    /// The limit order price when take profit price is triggered
217    #[serde(default, deserialize_with = "option_decimal")]
218    pub tp_limit_price: Option<Decimal>,
219    /// The limit order price when stop loss price is triggered
220    #[serde(default, deserialize_with = "option_decimal")]
221    pub sl_limit_price: Option<Decimal>,
222    /// The price type to trigger take profit
223    #[serde(default, deserialize_with = "empty_string_as_none")]
224    pub tp_trigger_by: Option<TriggerBy>,
225    /// The price type to trigger stop loss
226    #[serde(default, deserialize_with = "empty_string_as_none")]
227    pub sl_trigger_by: Option<TriggerBy>,
228    /// Trigger direction. 1: rise, 2: fall
229    pub trigger_direction: TriggerDirection,
230    /// The price type of trigger price
231    #[serde(default, deserialize_with = "empty_string_as_none")]
232    pub trigger_by: Option<TriggerBy>,
233    /// Last price when place the order, Spot is not applicable
234    #[serde(default, deserialize_with = "option_decimal")]
235    pub last_price_on_created: Option<Decimal>,
236    /// Last price when place the order, Spot has this field only
237    #[serde(default, deserialize_with = "option_decimal")]
238    pub base_price: Option<Decimal>,
239    /// Reduce only. true means reduce position size
240    pub reduce_only: bool,
241    /// Close on trigger. What is a close on trigger order?
242    pub close_on_trigger: bool,
243    /// Place type, option used. iv, price
244    #[serde(default, deserialize_with = "empty_string_as_none")]
245    pub place_type: Option<PlaceType>,
246    /// SMP execution type
247    pub smp_type: SmpType,
248    /// Smp group ID. If the UID has no group, it is 0 by default
249    pub smp_group: i64,
250    /// The counterparty's orderID which triggers this SMP execution
251    #[serde(default, deserialize_with = "empty_string_as_none")]
252    pub smp_order_id: Option<String>,
253    /// Order created timestamp (ms)
254    #[serde(deserialize_with = "number")]
255    pub created_time: Timestamp,
256    /// Order updated timestamp (ms)
257    #[serde(deserialize_with = "number")]
258    pub updated_time: Timestamp,
259}
260
261impl Order {
262    pub fn is_open_status(&self) -> bool {
263        self.order_status.is_open()
264    }
265    pub fn is_closed_status(&self) -> bool {
266        self.order_status.is_closed()
267    }
268    pub fn update(&mut self, msg: OrderMsg) {
269        self.order_id = msg.order_id;
270        self.order_link_id = msg.order_link_id;
271        self.block_trade_id = msg.block_trade_id;
272        self.symbol = msg.symbol;
273        self.price = msg.price;
274        self.qty = msg.qty;
275        self.side = msg.side;
276        self.is_leverage = msg.is_leverage;
277        self.position_idx = msg.position_idx;
278        self.order_status = msg.order_status;
279        self.create_type = msg.create_type;
280        self.cancel_type = msg.cancel_type;
281        self.reject_reason = msg.reject_reason;
282        self.avg_price = msg.avg_price;
283        if let Some(leaves_qty) = msg.leaves_qty {
284            self.leaves_qty = leaves_qty;
285        }
286        if let Some(leaves_value) = msg.leaves_value {
287            self.leaves_value = leaves_value;
288        }
289        self.cum_exec_qty = msg.cum_exec_qty;
290        self.cum_exec_value = msg.cum_exec_value;
291        self.cum_exec_fee = msg.cum_exec_fee;
292        self.time_in_force = msg.time_in_force;
293        self.order_type = msg.order_type;
294        self.stop_order_type = msg.stop_order_type;
295        self.order_iv = msg.order_iv;
296        self.market_unit = msg.market_unit;
297        self.trigger_price = msg.trigger_price;
298        self.take_profit = msg.take_profit;
299        self.stop_loss = msg.stop_loss;
300        self.tpsl_mode = msg.tpsl_mode;
301        self.oco_trigger_by = msg.oco_trigger_by;
302        self.tp_limit_price = msg.tp_limit_price;
303        self.sl_limit_price = msg.sl_limit_price;
304        self.tp_trigger_by = msg.tp_trigger_by;
305        self.sl_trigger_by = msg.sl_trigger_by;
306        self.trigger_direction = msg.trigger_direction;
307        self.trigger_by = msg.trigger_by;
308        self.last_price_on_created = msg.last_price_on_created;
309        // TODO: self.base_price
310        self.reduce_only = msg.reduce_only;
311        self.close_on_trigger = msg.close_on_trigger;
312        self.place_type = msg.place_type;
313        self.smp_type = msg.smp_type;
314        self.smp_group = msg.smp_group;
315        self.smp_order_id = msg.smp_order_id;
316        self.created_time = msg.created_time;
317        self.updated_time = msg.updated_time;
318    }
319}
320
321#[derive(Debug, Serialize)]
322#[serde(rename_all = "camelCase")]
323pub struct PlaceOrderRequest {
324    /// Product type
325    /// UTA2.0, UTA1.0: linear, inverse, spot, option
326    /// classic account: linear, inverse, spot
327    pub category: Category,
328    /// Symbol name, like BTCUSDT, uppercase only
329    pub symbol: String,
330    // Whether to borrow. Unified account Spot trading only.
331    /// 0(default): false, spot trading
332    /// 1: true, margin trading, make sure you turn on margin trading, and set the relevant currency as collateral
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub is_leverage: Option<i64>,
335    /// Buy, Sell
336    pub side: Side,
337    /// Market, Limit
338    pub order_type: OrderType,
339    /// Order quantity
340    /// UTA account
341    /// Spot: Market Buy order by value by default, you can set marketUnit field to choose order by value or qty for market orders
342    /// Perps, Futures & Option: always order by qty
343    /// classic account
344    /// Spot: Market Buy order by value by default
345    /// Perps, Futures: always order by qty
346    /// 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
347    pub qty: Decimal,
348    /// Select the unit for qty when create Spot market orders for UTA account
349    /// baseCoin: for example, buy BTCUSDT, then "qty" unit is BTC
350    /// quoteCoin: for example, sell BTCUSDT, then "qty" unit is USDT
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub market_unit: Option<String>,
353    /// Slippage tolerance Type for market order, TickSize, Percent
354    /// take profit, stoploss, conditional orders are not supported
355    /// TickSize:
356    /// the highest price of Buy order = ask1 + slippageTolerance x tickSize;
357    /// the lowest price of Sell order = bid1 - slippageTolerance x tickSize
358    /// Percent:
359    /// the highest price of Buy order = ask1 x (1 + slippageTolerance x 0.01);
360    /// the lowest price of Sell order = bid1 x (1 - slippageTolerance x 0.01)
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub slippage_tolerance_type: Option<Decimal>,
363    /// Slippage tolerance value
364    /// TickSize: range is [1, 10000], integer only
365    /// Percent: range is [0.01, 10], up to 2 decimals
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub slippage_tolerance: Option<Decimal>,
368    /// Order price
369    /// Market order will ignore this field
370    /// Please check the min price and price precision from instrument info endpoint
371    /// If you have position, price needs to be better than liquidation price
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub price: Option<Decimal>,
374    /// Conditional order param. Used to identify the expected direction of the conditional order.
375    /// 1: triggered when market price rises to triggerPrice
376    /// 2: triggered when market price falls to triggerPrice
377    /// Valid for linear & inverse
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub trigger_direction: Option<TriggerDirection>,
380    /// If it is not passed, Order by default.
381    /// Order
382    /// tpslOrder: Spot TP/SL order, the assets are occupied even before the order is triggered
383    /// 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
384    /// Valid for spot only
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub order_filter: Option<String>,
387    /// For Perps & Futures, it is the conditional order trigger price. If you expect the price to rise to trigger your conditional order, make sure:
388    /// triggerPrice > market price
389    /// Else, triggerPrice < market price
390    /// For spot, it is the TP/SL and Conditional order trigger price
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub trigger_price: Option<Decimal>,
393    /// Trigger price type, Conditional order param for Perps & Futures.
394    /// LastPrice
395    /// IndexPrice
396    /// MarkPrice
397    /// Valid for linear & inverse
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub trigger_by: Option<TriggerBy>,
400    /// 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
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub order_iv: Option<Decimal>,
403    /// Time in force
404    /// Market order will always use IOC
405    /// If not passed, GTC is used by default
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub time_in_force: Option<TimeInForce>,
408    /// Used to identify positions in different position modes. Under hedge-mode, this param is required
409    /// 0: one-way mode
410    /// 1: hedge-mode Buy side
411    /// 2: hedge-mode Sell side
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub position_idx: Option<PositionIdx>,
414    /// User customised order ID. A max of 36 characters. Combinations of numbers, letters (upper and lower cases), dashes, and underscores are supported.
415    /// Futures & Perps: orderLinkId rules:
416    /// optional param
417    /// always unique
418    /// option orderLinkId rules:
419    /// required param
420    /// always unique
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub order_link_id: Option<String>,
423    /// Take profit price
424    /// UTA: Spot Limit order supports take profit, stop loss or limit take profit, limit stop loss when creating an order
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub take_profit: Option<Decimal>,
427    /// Stop loss price
428    /// UTA: Spot Limit order supports take profit, stop loss or limit take profit, limit stop loss when creating an order
429    #[serde(skip_serializing_if = "Option::is_none")]
430    pub stop_loss: Option<Decimal>,
431    /// The price type to trigger take profit. MarkPrice, IndexPrice, default: LastPrice. Valid for linear & inverse
432    #[serde(skip_serializing_if = "Option::is_none")]
433    pub tp_trigger_by: Option<TriggerBy>,
434    /// The price type to trigger stop loss. MarkPrice, IndexPrice, default: LastPrice. Valid for linear & inverse
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub sl_trigger_by: Option<TriggerBy>,
437    /// What is a reduce-only order? true means your position can only reduce in size if this order is triggered.
438    /// You must specify it as true when you are about to close/reduce the position
439    /// When reduceOnly is true, take profit/stop loss cannot be set
440    /// Valid for linear, inverse & option
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub reduce_only: Option<bool>,
443    /// 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.
444    /// Valid for linear & inverse
445    #[serde(skip_serializing_if = "Option::is_none")]
446    pub close_on_trigger: Option<bool>,
447    /// Smp execution type. What is SMP?
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub smp_type: Option<SmpType>,
450    /// Market maker protection. option only. true means set the order as a market maker protection order. What is mmp?
451    #[serde(skip_serializing_if = "Option::is_none")]
452    pub mmp: Option<bool>,
453    /// TP/SL mode
454    /// Full: entire position for TP/SL. Then, tpOrderType or slOrderType must be Market
455    /// 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
456    /// Valid for linear & inverse
457    #[serde(skip_serializing_if = "Option::is_none")]
458    pub tpsl_mode: Option<TpslMode>,
459    /// The limit order price when take profit price is triggered
460    /// linear & inverse: only works when tpslMode=Partial and tpOrderType=Limit
461    /// Spot(UTA): it is required when the order has takeProfit and "tpOrderType"=Limit
462    #[serde(skip_serializing_if = "Option::is_none")]
463    pub tp_limit_price: Option<Decimal>,
464    /// The limit order price when stop loss price is triggered
465    /// linear & inverse: only works when tpslMode=Partial and slOrderType=Limit
466    /// Spot(UTA): it is required when the order has stopLoss and "slOrderType"=Limit
467    #[serde(skip_serializing_if = "Option::is_none")]
468    pub sl_limit_price: Option<Decimal>,
469    /// The order type when take profit is triggered
470    /// linear & inverse: Market(default), Limit. For tpslMode=Full, it only supports tpOrderType=Market
471    /// Spot(UTA):
472    /// Market: when you set "takeProfit",
473    /// Limit: when you set "takeProfit" and "tpLimitPrice"
474    #[serde(skip_serializing_if = "Option::is_none")]
475    pub tp_order_type: Option<OrderType>,
476    /// The order type when stop loss is triggered
477    /// linear & inverse: Market(default), Limit. For tpslMode=Full, it only supports slOrderType=Market
478    /// Spot(UTA):
479    /// Market: when you set "stopLoss",
480    /// Limit: when you set "stopLoss" and "slLimitPrice"
481    #[serde(skip_serializing_if = "Option::is_none")]
482    pub sl_order_type: Option<OrderType>,
483    /// Queue: use the order price on the orderbook in the same direction as the side
484    /// Counterparty: use the order price on the orderbook in the opposite direction as the side
485    /// Valid for linear & inverse
486    #[serde(skip_serializing_if = "Option::is_none")]
487    pub bbo_side_type: Option<String>,
488    /// 1,2,3,4,5 Valid for linear & inverse
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub bbo_level: Option<String>,
491}
492
493impl PlaceOrderRequest {
494    pub fn new(
495        category: Category,
496        symbol: String,
497        side: Side,
498        order_type: OrderType,
499        qty: Decimal,
500    ) -> Self {
501        Self {
502            category,
503            symbol,
504            is_leverage: None,
505            side,
506            order_type,
507            qty,
508            market_unit: None,
509            slippage_tolerance_type: None,
510            slippage_tolerance: None,
511            price: None,
512            trigger_direction: None,
513            order_filter: None,
514            trigger_price: None,
515            trigger_by: None,
516            order_iv: None,
517            time_in_force: None,
518            position_idx: None,
519            order_link_id: None,
520            take_profit: None,
521            stop_loss: None,
522            tp_trigger_by: None,
523            sl_trigger_by: None,
524            reduce_only: None,
525            close_on_trigger: None,
526            smp_type: None,
527            mmp: None,
528            tpsl_mode: None,
529            tp_limit_price: None,
530            sl_limit_price: None,
531            tp_order_type: None,
532            sl_order_type: None,
533            bbo_side_type: None,
534            bbo_level: None,
535        }
536    }
537
538    pub fn with_is_leverage(mut self, v: i64) -> Self {
539        self.is_leverage = Some(v);
540        self
541    }
542    pub fn with_market_unit(mut self, v: String) -> Self {
543        self.market_unit = Some(v);
544        self
545    }
546    pub fn with_slippage_tolerance_type(mut self, v: Decimal) -> Self {
547        self.slippage_tolerance_type = Some(v);
548        self
549    }
550    pub fn with_slippage_tolerance(mut self, v: Decimal) -> Self {
551        self.slippage_tolerance = Some(v);
552        self
553    }
554    pub fn with_price(mut self, v: Decimal) -> Self {
555        self.price = Some(v);
556        self
557    }
558    pub fn with_trigger_direction(mut self, v: TriggerDirection) -> Self {
559        self.trigger_direction = Some(v);
560        self
561    }
562    pub fn with_order_filter(mut self, v: String) -> Self {
563        self.order_filter = Some(v);
564        self
565    }
566    pub fn with_trigger_price(mut self, v: Decimal) -> Self {
567        self.trigger_price = Some(v);
568        self
569    }
570    pub fn with_trigger_by(mut self, v: TriggerBy) -> Self {
571        self.trigger_by = Some(v);
572        self
573    }
574    pub fn with_order_iv(mut self, v: Decimal) -> Self {
575        self.order_iv = Some(v);
576        self
577    }
578    pub fn with_time_in_force(mut self, v: TimeInForce) -> Self {
579        self.time_in_force = Some(v);
580        self
581    }
582    pub fn with_position_idx(mut self, v: PositionIdx) -> Self {
583        self.position_idx = Some(v);
584        self
585    }
586    pub fn with_order_link_id(mut self, v: String) -> Self {
587        self.order_link_id = Some(v);
588        self
589    }
590    pub fn with_take_profit(mut self, v: Decimal) -> Self {
591        self.take_profit = Some(v);
592        self
593    }
594    pub fn with_stop_loss(mut self, v: Decimal) -> Self {
595        self.stop_loss = Some(v);
596        self
597    }
598    pub fn with_tp_trigger_by(mut self, v: TriggerBy) -> Self {
599        self.tp_trigger_by = Some(v);
600        self
601    }
602    pub fn with_sl_trigger_by(mut self, v: TriggerBy) -> Self {
603        self.sl_trigger_by = Some(v);
604        self
605    }
606    pub fn with_reduce_only(mut self, v: bool) -> Self {
607        self.reduce_only = Some(v);
608        self
609    }
610    pub fn with_close_on_trigger(mut self, v: bool) -> Self {
611        self.close_on_trigger = Some(v);
612        self
613    }
614    pub fn with_smp_type(mut self, v: SmpType) -> Self {
615        self.smp_type = Some(v);
616        self
617    }
618    pub fn with_mmp(mut self, v: bool) -> Self {
619        self.mmp = Some(v);
620        self
621    }
622    pub fn with_tpsl_mode(mut self, v: TpslMode) -> Self {
623        self.tpsl_mode = Some(v);
624        self
625    }
626    pub fn with_tp_limit_price(mut self, v: Decimal) -> Self {
627        self.tp_limit_price = Some(v);
628        self
629    }
630    pub fn with_sl_limit_price(mut self, v: Decimal) -> Self {
631        self.sl_limit_price = Some(v);
632        self
633    }
634    pub fn with_tp_order_type(mut self, v: OrderType) -> Self {
635        self.tp_order_type = Some(v);
636        self
637    }
638    pub fn with_sl_order_type(mut self, v: OrderType) -> Self {
639        self.sl_order_type = Some(v);
640        self
641    }
642    pub fn with_bbo_side_type(mut self, v: String) -> Self {
643        self.bbo_side_type = Some(v);
644        self
645    }
646    pub fn with_bbo_level(mut self, v: String) -> Self {
647        self.bbo_level = Some(v);
648        self
649    }
650}
651
652#[derive(Debug, Deserialize, PartialEq)]
653#[serde(rename_all = "camelCase")]
654pub struct PlaceOrderResponse {
655    /// Order ID
656    pub order_id: String,
657    /// User customised order ID
658    pub order_link_id: String,
659}
660
661#[derive(Debug, Serialize)]
662#[serde(rename_all = "camelCase")]
663pub struct AmendOrderRequest {
664    pub category: Category,
665    pub symbol: String,
666    #[serde(skip_serializing_if = "Option::is_none")]
667    pub order_id: Option<String>,
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub order_link_id: Option<String>,
670    #[serde(skip_serializing_if = "Option::is_none")]
671    pub qty: Option<Decimal>,
672    #[serde(skip_serializing_if = "Option::is_none")]
673    pub price: Option<Decimal>,
674}
675
676impl AmendOrderRequest {
677    pub fn new(category: Category, symbol: String) -> Self {
678        Self {
679            category,
680            symbol,
681            order_id: None,
682            order_link_id: None,
683            qty: None,
684            price: None,
685        }
686    }
687
688    pub fn with_order_id(mut self, v: String) -> Self {
689        self.order_id = Some(v);
690        self
691    }
692    pub fn with_order_link_id(mut self, v: String) -> Self {
693        self.order_link_id = Some(v);
694        self
695    }
696    pub fn with_qty(mut self, v: Decimal) -> Self {
697        self.qty = Some(v);
698        self
699    }
700    pub fn with_price(mut self, v: Decimal) -> Self {
701        self.price = Some(v);
702        self
703    }
704}
705
706#[derive(Debug, Deserialize, PartialEq)]
707#[serde(rename_all = "camelCase")]
708pub struct AmendOrderResponse {
709    /// Order ID
710    pub order_id: String,
711    #[serde(default, deserialize_with = "empty_string_as_none")]
712    /// User customised order ID
713    pub order_link_id: Option<String>,
714}
715
716#[derive(Debug, Serialize)]
717#[serde(rename_all = "camelCase")]
718pub struct CancelOrderRequest {
719    /// Product type. linear, inverse, spot, option
720    pub category: Category,
721    /// Symbol name, like BTCUSDT, uppercase only
722    pub symbol: String,
723    /// Order ID. Either orderId or orderLinkId is required
724    #[serde(skip_serializing_if = "Option::is_none")]
725    pub order_id: Option<String>,
726    /// User customised order ID. Either orderId or orderLinkId is required
727    #[serde(skip_serializing_if = "Option::is_none")]
728    pub order_link_id: Option<String>,
729    /// Spot trading only
730    /// Order, tpslOrder, StopOrder
731    /// If not passed, Order by default
732    #[serde(skip_serializing_if = "Option::is_none")]
733    pub order_filter: Option<OrderFilter>,
734}
735
736impl CancelOrderRequest {
737    pub fn new(category: Category, symbol: String) -> Self {
738        Self {
739            category,
740            symbol,
741            order_id: None,
742            order_link_id: None,
743            order_filter: None,
744        }
745    }
746
747    pub fn with_order_id(mut self, v: String) -> Self {
748        self.order_id = Some(v);
749        self
750    }
751    pub fn with_order_link_id(mut self, v: String) -> Self {
752        self.order_link_id = Some(v);
753        self
754    }
755    pub fn with_order_filter(mut self, v: OrderFilter) -> Self {
756        self.order_filter = Some(v);
757        self
758    }
759}
760
761#[derive(Debug, Deserialize, PartialEq)]
762#[serde(rename_all = "camelCase")]
763pub struct CancelOrderResponse {
764    /// Order ID
765    pub order_id: String,
766    /// User customised order ID
767    #[serde(default, deserialize_with = "empty_string_as_none")]
768    pub order_link_id: Option<String>,
769}
770
771// ── Cancel All Orders ────────────────────────────────────────────────────────
772
773#[derive(Debug, Serialize)]
774#[serde(rename_all = "camelCase")]
775pub struct CancelAllOrdersRequest {
776    pub category: Category,
777    #[serde(skip_serializing_if = "Option::is_none")]
778    pub symbol: Option<String>,
779    #[serde(skip_serializing_if = "Option::is_none")]
780    pub base_coin: Option<String>,
781    #[serde(skip_serializing_if = "Option::is_none")]
782    pub settle_coin: Option<String>,
783    #[serde(skip_serializing_if = "Option::is_none")]
784    pub order_filter: Option<OrderFilter>,
785    /// For futures only: TakeProfit, StopLoss, TrailingStop
786    #[serde(skip_serializing_if = "Option::is_none")]
787    pub stop_order_type: Option<StopOrderType>,
788}
789
790impl CancelAllOrdersRequest {
791    pub fn new(category: Category) -> Self {
792        Self {
793            category,
794            symbol: None,
795            base_coin: None,
796            settle_coin: None,
797            order_filter: None,
798            stop_order_type: None,
799        }
800    }
801
802    pub fn with_symbol(mut self, v: String) -> Self {
803        self.symbol = Some(v);
804        self
805    }
806    pub fn with_base_coin(mut self, v: String) -> Self {
807        self.base_coin = Some(v);
808        self
809    }
810    pub fn with_settle_coin(mut self, v: String) -> Self {
811        self.settle_coin = Some(v);
812        self
813    }
814    pub fn with_order_filter(mut self, v: OrderFilter) -> Self {
815        self.order_filter = Some(v);
816        self
817    }
818    pub fn with_stop_order_type(mut self, v: StopOrderType) -> Self {
819        self.stop_order_type = Some(v);
820        self
821    }
822}
823
824/// Response for cancel-all: a flat list of cancelled order IDs.
825pub type CancelAllOrdersResponse = List<CancelOrderResponse>;
826
827// ── Order History ────────────────────────────────────────────────────────────
828
829#[derive(Clone, Debug, Serialize)]
830#[serde(rename_all = "camelCase")]
831pub struct GetOrderHistoryParams {
832    pub category: Category,
833    #[serde(skip_serializing_if = "Option::is_none")]
834    pub symbol: Option<String>,
835    #[serde(skip_serializing_if = "Option::is_none")]
836    pub base_coin: Option<String>,
837    #[serde(skip_serializing_if = "Option::is_none")]
838    pub settle_coin: Option<String>,
839    #[serde(skip_serializing_if = "Option::is_none")]
840    pub order_id: Option<String>,
841    #[serde(skip_serializing_if = "Option::is_none")]
842    pub order_link_id: Option<String>,
843    #[serde(skip_serializing_if = "Option::is_none")]
844    pub order_filter: Option<OrderFilter>,
845    #[serde(skip_serializing_if = "Option::is_none")]
846    pub order_status: Option<OrderStatus>,
847    /// Start timestamp (ms)
848    #[serde(skip_serializing_if = "Option::is_none")]
849    pub start_time: Option<Timestamp>,
850    /// End timestamp (ms)
851    #[serde(skip_serializing_if = "Option::is_none")]
852    pub end_time: Option<Timestamp>,
853    /// [1, 50]. Default: 20
854    #[serde(skip_serializing_if = "Option::is_none")]
855    pub limit: Option<i32>,
856    #[serde(skip_serializing_if = "Option::is_none")]
857    pub cursor: Option<String>,
858}
859
860impl GetOrderHistoryParams {
861    pub fn new(category: Category) -> Self {
862        Self {
863            category,
864            symbol: None,
865            base_coin: None,
866            settle_coin: None,
867            order_id: None,
868            order_link_id: None,
869            order_filter: None,
870            order_status: None,
871            start_time: None,
872            end_time: None,
873            limit: None,
874            cursor: None,
875        }
876    }
877
878    pub fn with_symbol(mut self, v: String) -> Self {
879        self.symbol = Some(v);
880        self
881    }
882    pub fn with_base_coin(mut self, v: String) -> Self {
883        self.base_coin = Some(v);
884        self
885    }
886    pub fn with_settle_coin(mut self, v: String) -> Self {
887        self.settle_coin = Some(v);
888        self
889    }
890    pub fn with_order_id(mut self, v: String) -> Self {
891        self.order_id = Some(v);
892        self
893    }
894    pub fn with_order_link_id(mut self, v: String) -> Self {
895        self.order_link_id = Some(v);
896        self
897    }
898    pub fn with_order_filter(mut self, v: OrderFilter) -> Self {
899        self.order_filter = Some(v);
900        self
901    }
902    pub fn with_order_status(mut self, v: OrderStatus) -> Self {
903        self.order_status = Some(v);
904        self
905    }
906    pub fn with_start_time(mut self, v: Timestamp) -> Self {
907        self.start_time = Some(v);
908        self
909    }
910    pub fn with_end_time(mut self, v: Timestamp) -> Self {
911        self.end_time = Some(v);
912        self
913    }
914    pub fn with_limit(mut self, v: i32) -> Self {
915        self.limit = Some(v);
916        self
917    }
918    pub fn with_cursor(mut self, v: String) -> Self {
919        self.cursor = Some(v);
920        self
921    }
922}
923
924// ── Batch Orders ─────────────────────────────────────────────────────────────
925
926#[derive(Debug, Serialize)]
927#[serde(rename_all = "camelCase")]
928pub struct PlaceOrderBatchRequest {
929    pub category: Category,
930    pub request: Vec<PlaceOrderRequest>,
931}
932
933#[derive(Debug, Serialize)]
934#[serde(rename_all = "camelCase")]
935pub struct AmendOrderBatchRequest {
936    pub category: Category,
937    pub request: Vec<AmendOrderRequest>,
938}
939
940#[derive(Debug, Serialize)]
941#[serde(rename_all = "camelCase")]
942pub struct CancelOrderBatchRequest {
943    pub category: Category,
944    pub request: Vec<CancelOrderRequest>,
945}
946
947#[derive(Debug, Deserialize, PartialEq)]
948#[serde(rename_all = "camelCase")]
949pub struct PlaceOrderBatchResult {
950    pub category: Category,
951    pub symbol: String,
952    pub order_id: String,
953    #[serde(default, deserialize_with = "empty_string_as_none")]
954    pub order_link_id: Option<String>,
955    /// Order creation timestamp (ms). Note: Bybit uses the non-standard key "createAt".
956    #[serde(rename = "createAt", default)]
957    pub create_at: Option<Timestamp>,
958}
959
960#[derive(Debug, Deserialize, PartialEq)]
961#[serde(rename_all = "camelCase")]
962pub struct AmendOrderBatchResult {
963    pub category: Category,
964    pub symbol: String,
965    pub order_id: String,
966    #[serde(default, deserialize_with = "empty_string_as_none")]
967    pub order_link_id: Option<String>,
968}
969
970#[derive(Debug, Deserialize, PartialEq)]
971#[serde(rename_all = "camelCase")]
972pub struct CancelOrderBatchResult {
973    pub category: Category,
974    pub symbol: String,
975    pub order_id: String,
976    #[serde(default, deserialize_with = "empty_string_as_none")]
977    pub order_link_id: Option<String>,
978}