Skip to main content

nautilus_bybit/http/
query.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Builder types for Bybit REST query parameters and filters.
17
18use derive_builder::Builder;
19use serde::{Deserialize, Serialize};
20
21use crate::common::{
22    enums::{
23        BybitAccountType, BybitBboSideType, BybitExecType, BybitInstrumentStatus,
24        BybitKlineInterval, BybitMarginMode, BybitMarketUnit, BybitOpenOnly, BybitOptionType,
25        BybitOrderFilter, BybitOrderSide, BybitOrderStatus, BybitOrderType, BybitPositionIdx,
26        BybitPositionMode, BybitProductType, BybitSmpType, BybitStopOrderType, BybitTimeInForce,
27        BybitTpSlMode, BybitTriggerDirection, BybitTriggerType,
28    },
29    parse::opt_bool_as_int,
30};
31
32/// Query parameters for `GET /v5/market/instruments-info`.
33///
34/// # References
35/// - <https://bybit-exchange.github.io/docs/v5/market/instrument>
36#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
37#[serde(rename_all = "camelCase")]
38#[builder(default)]
39#[builder(setter(into))]
40pub struct BybitInstrumentsInfoParams {
41    pub category: BybitProductType,
42    #[builder(setter(strip_option))]
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub symbol: Option<String>,
45    #[builder(setter(strip_option))]
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub status: Option<BybitInstrumentStatus>,
48    #[builder(setter(strip_option))]
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub base_coin: Option<String>,
51    #[builder(setter(strip_option))]
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub limit: Option<u32>,
54    #[builder(setter(strip_option))]
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub cursor: Option<String>,
57}
58
59/// Query parameters for `GET /v5/market/tickers`.
60///
61/// # References
62/// - <https://bybit-exchange.github.io/docs/v5/market/tickers>
63#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
64#[serde(rename_all = "camelCase")]
65#[builder(default)]
66#[builder(setter(into))]
67pub struct BybitTickersParams {
68    pub category: BybitProductType,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    #[builder(setter(strip_option))]
71    pub symbol: Option<String>,
72    #[serde(skip_serializing_if = "Option::is_none")]
73    #[builder(setter(strip_option))]
74    pub base_coin: Option<String>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    #[builder(setter(strip_option))]
77    pub exp_date: Option<String>,
78}
79
80/// Query parameters for `GET /v5/market/kline`.
81///
82/// # References
83/// - <https://bybit-exchange.github.io/docs/v5/market/kline>
84#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
85#[serde(rename_all = "camelCase")]
86#[builder(setter(into, strip_option), default)]
87pub struct BybitKlinesParams {
88    pub category: BybitProductType,
89    pub symbol: String,
90    pub interval: BybitKlineInterval,
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub start: Option<i64>,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub end: Option<i64>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub limit: Option<u32>,
97}
98
99impl Default for BybitKlinesParams {
100    fn default() -> Self {
101        Self {
102            category: BybitProductType::Linear,
103            symbol: String::new(),
104            interval: BybitKlineInterval::Minute1,
105            start: None,
106            end: None,
107            limit: None,
108        }
109    }
110}
111
112/// Query parameters for `GET /v5/market/recent-trade`.
113///
114/// # References
115/// - <https://bybit-exchange.github.io/docs/v5/market/recent-trade>
116#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
117#[serde(rename_all = "camelCase")]
118#[builder(setter(into, strip_option), default)]
119pub struct BybitTradesParams {
120    pub category: BybitProductType,
121    pub symbol: String,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub base_coin: Option<String>,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub option_type: Option<BybitOptionType>,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub limit: Option<u32>,
128}
129
130impl Default for BybitTradesParams {
131    fn default() -> Self {
132        Self {
133            category: BybitProductType::Linear,
134            symbol: String::new(),
135            base_coin: None,
136            option_type: None,
137            limit: None,
138        }
139    }
140}
141
142/// Query parameters for `GET /v5/market/funding/history`.
143///
144/// # References
145/// - <https://bybit-exchange.github.io/docs/v5/market/history-fund-rate>
146#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
147#[serde(rename_all = "camelCase")]
148#[builder(setter(into, strip_option), default)]
149pub struct BybitFundingParams {
150    pub category: BybitProductType,
151    pub symbol: String,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub start_time: Option<i64>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub end_time: Option<i64>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub limit: Option<u32>,
158}
159
160impl Default for BybitFundingParams {
161    fn default() -> Self {
162        Self {
163            category: BybitProductType::Linear,
164            symbol: String::new(),
165            start_time: None,
166            end_time: None,
167            limit: None,
168        }
169    }
170}
171
172/// Query parameters for `GET /v5/market/orderbook`.
173///
174/// # References
175/// - <https://bybit-exchange.github.io/docs/v5/market/orderbook>
176#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
177#[builder(setter(into, strip_option), default)]
178pub struct BybitOrderbookParams {
179    pub category: BybitProductType,
180    pub symbol: String,
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub limit: Option<u32>,
183}
184
185impl Default for BybitOrderbookParams {
186    fn default() -> Self {
187        Self {
188            category: BybitProductType::Linear,
189            symbol: String::new(),
190            limit: None,
191        }
192    }
193}
194
195/// Query parameters for `GET /v5/asset/coin/query-info`.
196///
197/// # References
198/// - <https://bybit-exchange.github.io/docs/v5/asset/coin-info>
199#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
200#[serde(rename_all = "camelCase")]
201#[builder(default)]
202#[builder(setter(into))]
203pub struct BybitCoinInfoParams {
204    #[serde(skip_serializing_if = "Option::is_none")]
205    #[builder(setter(strip_option))]
206    pub coin: Option<String>,
207}
208
209/// Query parameters for `GET /v5/account/fee-rate`.
210///
211/// # References
212/// - <https://bybit-exchange.github.io/docs/v5/account/fee-rate>
213#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
214#[serde(rename_all = "camelCase")]
215#[builder(default)]
216#[builder(setter(into))]
217pub struct BybitFeeRateParams {
218    pub category: BybitProductType,
219    #[builder(setter(strip_option))]
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub symbol: Option<String>,
222    #[builder(setter(strip_option))]
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub base_coin: Option<String>,
225}
226
227/// Query parameters for `GET /v5/account/wallet-balance`.
228///
229/// # References
230/// - <https://bybit-exchange.github.io/docs/v5/account/wallet-balance>
231#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
232#[serde(rename_all = "camelCase")]
233pub struct BybitWalletBalanceParams {
234    pub account_type: BybitAccountType,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    #[builder(setter(strip_option))]
237    pub coin: Option<String>,
238}
239
240/// Query parameters for `GET /v5/position/list`.
241///
242/// # References
243/// - <https://bybit-exchange.github.io/docs/v5/position>
244#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
245#[serde(rename_all = "camelCase")]
246#[builder(default)]
247#[builder(setter(into))]
248pub struct BybitPositionListParams {
249    pub category: BybitProductType,
250    #[serde(skip_serializing_if = "Option::is_none")]
251    #[builder(setter(strip_option))]
252    pub symbol: Option<String>,
253    #[serde(skip_serializing_if = "Option::is_none")]
254    #[builder(setter(strip_option))]
255    pub base_coin: Option<String>,
256    #[serde(skip_serializing_if = "Option::is_none")]
257    #[builder(setter(strip_option))]
258    pub settle_coin: Option<String>,
259    #[serde(skip_serializing_if = "Option::is_none")]
260    #[builder(setter(strip_option))]
261    pub limit: Option<u32>,
262    #[serde(skip_serializing_if = "Option::is_none")]
263    #[builder(setter(strip_option))]
264    pub cursor: Option<String>,
265}
266
267/// Body parameters for `POST /v5/account/set-margin-mode`.
268///
269/// # References
270/// - <https://bybit-exchange.github.io/docs/v5/account/set-margin-mode>
271#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
272#[serde(rename_all = "camelCase")]
273pub struct BybitSetMarginModeParams {
274    pub set_margin_mode: BybitMarginMode,
275}
276
277/// Body parameters for `POST /v5/position/set-leverage`.
278///
279/// # References
280/// - <https://bybit-exchange.github.io/docs/v5/position/leverage>
281#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
282#[serde(rename_all = "camelCase")]
283pub struct BybitSetLeverageParams {
284    pub category: BybitProductType,
285    pub symbol: String,
286    pub buy_leverage: String,
287    pub sell_leverage: String,
288}
289
290/// Body parameters for `POST /v5/position/switch-mode`.
291///
292/// # References
293/// - <https://bybit-exchange.github.io/docs/v5/position/position-mode>
294#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
295#[serde(rename_all = "camelCase")]
296pub struct BybitSwitchModeParams {
297    pub category: BybitProductType,
298    #[builder(default)]
299    #[serde(skip_serializing_if = "Option::is_none")]
300    #[builder(setter(strip_option))]
301    pub symbol: Option<String>,
302    #[builder(default)]
303    #[serde(skip_serializing_if = "Option::is_none")]
304    #[builder(setter(strip_option))]
305    pub coin: Option<String>,
306    pub mode: BybitPositionMode,
307}
308
309/// Body parameters for `POST /v5/position/trading-stop`.
310///
311/// # References
312/// - <https://bybit-exchange.github.io/docs/v5/position/trading-stop>
313#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
314#[serde(rename_all = "camelCase")]
315pub struct BybitSetTradingStopParams {
316    pub category: BybitProductType,
317    pub symbol: String,
318    pub position_idx: BybitPositionIdx,
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub take_profit: Option<String>,
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub stop_loss: Option<String>,
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub trailing_stop: Option<String>,
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub tp_trigger_by: Option<BybitTriggerType>,
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub sl_trigger_by: Option<BybitTriggerType>,
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub active_price: Option<String>,
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub tpsl_mode: Option<BybitTpSlMode>,
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub tp_size: Option<String>,
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub sl_size: Option<String>,
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub tp_limit_price: Option<String>,
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub sl_limit_price: Option<String>,
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub tp_order_type: Option<BybitOrderType>,
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub sl_order_type: Option<BybitOrderType>,
345}
346
347/// Body parameters for `POST /v5/account/borrow`.
348///
349/// # References
350///
351/// - <https://bybit-exchange.github.io/docs/v5/account/borrow>
352#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
353#[serde(rename_all = "camelCase")]
354pub struct BybitBorrowParams {
355    pub coin: String,
356    pub amount: String,
357}
358
359/// Body parameters for `POST /v5/account/no-convert-repay`.
360///
361/// # References
362///
363/// - <https://bybit-exchange.github.io/docs/v5/account/no-convert-repay>
364#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
365#[serde(rename_all = "camelCase")]
366pub struct BybitNoConvertRepayParams {
367    pub coin: String,
368    #[serde(skip_serializing_if = "Option::is_none")]
369    #[builder(setter(strip_option), default)]
370    pub amount: Option<String>,
371}
372
373/// Order entry payload for `POST /v5/order/create-batch`.
374///
375/// # References
376/// - <https://bybit-exchange.github.io/docs/v5/order/batch-place>
377#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
378#[serde(rename_all = "camelCase")]
379pub struct BybitBatchPlaceOrderEntry {
380    pub symbol: String,
381    #[builder(default)]
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub is_leverage: Option<i32>,
384    pub side: BybitOrderSide,
385    pub order_type: BybitOrderType,
386    pub qty: String,
387    #[builder(default)]
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub price: Option<String>,
390    #[builder(default)]
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub market_unit: Option<BybitMarketUnit>,
393    #[builder(default)]
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub trigger_direction: Option<BybitTriggerDirection>,
396    #[builder(default)]
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub order_filter: Option<BybitOrderFilter>,
399    #[builder(default)]
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub trigger_price: Option<String>,
402    #[builder(default)]
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub trigger_by: Option<BybitTriggerType>,
405    #[builder(default)]
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub order_iv: Option<String>,
408    #[builder(default)]
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub time_in_force: Option<BybitTimeInForce>,
411    #[builder(default)]
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub position_idx: Option<BybitPositionIdx>,
414    #[builder(default)]
415    #[serde(skip_serializing_if = "Option::is_none")]
416    #[builder(setter(strip_option))]
417    pub order_link_id: Option<String>,
418    #[builder(default)]
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub take_profit: Option<String>,
421    #[builder(default)]
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub stop_loss: Option<String>,
424    #[builder(default)]
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub tp_trigger_by: Option<BybitTriggerType>,
427    #[builder(default)]
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub sl_trigger_by: Option<BybitTriggerType>,
430    #[builder(default)]
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub reduce_only: Option<bool>,
433    #[builder(default)]
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub close_on_trigger: Option<bool>,
436    #[builder(default)]
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub smp_type: Option<BybitSmpType>,
439    #[builder(default)]
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub mmp: Option<bool>,
442    #[builder(default)]
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub tpsl_mode: Option<BybitTpSlMode>,
445    #[builder(default)]
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub tp_limit_price: Option<String>,
448    #[builder(default)]
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub sl_limit_price: Option<String>,
451    #[builder(default)]
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub tp_order_type: Option<BybitOrderType>,
454    #[builder(default)]
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub sl_order_type: Option<BybitOrderType>,
457    #[builder(default)]
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub bbo_side_type: Option<BybitBboSideType>,
460    #[builder(default)]
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub bbo_level: Option<String>,
463}
464
465/// Body parameters for `POST /v5/order/create-batch`.
466///
467/// # References
468/// - <https://bybit-exchange.github.io/docs/v5/order/batch-place>
469#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
470#[serde(rename_all = "camelCase")]
471pub struct BybitBatchPlaceOrderParams {
472    pub category: BybitProductType,
473    pub request: Vec<BybitBatchPlaceOrderEntry>,
474}
475
476/// Native TP/SL and option-specific fields that map onto the `POST /v5/order/create` entry.
477///
478/// Bundled to keep the `submit_order` signature manageable, and to give the demo HTTP path
479/// access to the same fields the mainnet WS path supports via
480/// [`crate::websocket::messages::BybitWsPlaceOrderParams`]. All fields are optional; populated
481/// fields are written onto the entry builder as-is. `tpsl_mode` defaults to `Full` upstream when
482/// only `take_profit` / `stop_loss` are set without an explicit mode.
483///
484/// `tp_trigger_price` / `sl_trigger_price` are intentionally absent: the create-order entry does
485/// not carry them (the mainnet WS Trade API does, via separate fields).
486#[derive(Debug, Clone, Default)]
487pub struct BybitNativeTpSlParams {
488    pub take_profit: Option<String>,
489    pub stop_loss: Option<String>,
490    pub tp_trigger_by: Option<BybitTriggerType>,
491    pub sl_trigger_by: Option<BybitTriggerType>,
492    pub tp_order_type: Option<BybitOrderType>,
493    pub sl_order_type: Option<BybitOrderType>,
494    pub tp_limit_price: Option<String>,
495    pub sl_limit_price: Option<String>,
496    pub tpsl_mode: Option<BybitTpSlMode>,
497    pub close_on_trigger: Option<bool>,
498    pub order_iv: Option<String>,
499    pub mmp: Option<bool>,
500}
501
502impl BybitNativeTpSlParams {
503    /// Returns `true` if no TP/SL or option-specific field is set.
504    #[must_use]
505    pub fn is_empty(&self) -> bool {
506        self.take_profit.is_none()
507            && self.stop_loss.is_none()
508            && self.tp_trigger_by.is_none()
509            && self.sl_trigger_by.is_none()
510            && self.tp_order_type.is_none()
511            && self.sl_order_type.is_none()
512            && self.tp_limit_price.is_none()
513            && self.sl_limit_price.is_none()
514            && self.tpsl_mode.is_none()
515            && self.close_on_trigger.is_none()
516            && self.order_iv.is_none()
517            && self.mmp.is_none()
518    }
519}
520
521/// Body parameters for `POST /v5/order/create`.
522///
523/// # References
524/// - <https://bybit-exchange.github.io/docs/v5/order/create-order>
525#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
526#[serde(rename_all = "camelCase")]
527pub struct BybitPlaceOrderParams {
528    #[serde(flatten)]
529    pub order: BybitBatchPlaceOrderEntry,
530    pub category: BybitProductType,
531    #[builder(default)]
532    #[serde(skip_serializing_if = "Option::is_none")]
533    pub slippage_tolerance_type: Option<String>,
534    #[builder(default)]
535    #[serde(skip_serializing_if = "Option::is_none")]
536    pub slippage_tolerance: Option<String>,
537}
538
539/// Amend entry for `POST /v5/order/amend-batch`.
540///
541/// # References
542/// - <https://bybit-exchange.github.io/docs/v5/order/batch-amend>
543#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
544#[serde(rename_all = "camelCase")]
545pub struct BybitBatchAmendOrderEntry {
546    pub symbol: String,
547    #[builder(default)]
548    #[serde(skip_serializing_if = "Option::is_none")]
549    #[builder(setter(strip_option))]
550    pub order_id: Option<String>,
551    #[builder(default)]
552    #[serde(skip_serializing_if = "Option::is_none")]
553    #[builder(setter(strip_option))]
554    pub order_link_id: Option<String>,
555    #[builder(default)]
556    #[serde(skip_serializing_if = "Option::is_none")]
557    pub order_iv: Option<String>,
558    #[builder(default)]
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub trigger_price: Option<String>,
561    #[builder(default)]
562    #[serde(skip_serializing_if = "Option::is_none")]
563    pub qty: Option<String>,
564    #[builder(default)]
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub price: Option<String>,
567    #[builder(default)]
568    #[serde(skip_serializing_if = "Option::is_none")]
569    pub tpsl_mode: Option<BybitTpSlMode>,
570    #[builder(default)]
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub take_profit: Option<String>,
573    #[builder(default)]
574    #[serde(skip_serializing_if = "Option::is_none")]
575    pub stop_loss: Option<String>,
576    #[builder(default)]
577    #[serde(skip_serializing_if = "Option::is_none")]
578    pub tp_trigger_by: Option<BybitTriggerType>,
579    #[builder(default)]
580    #[serde(skip_serializing_if = "Option::is_none")]
581    pub sl_trigger_by: Option<BybitTriggerType>,
582    #[builder(default)]
583    #[serde(skip_serializing_if = "Option::is_none")]
584    pub trigger_by: Option<BybitTriggerType>,
585    #[builder(default)]
586    #[serde(skip_serializing_if = "Option::is_none")]
587    pub tp_limit_price: Option<String>,
588    #[builder(default)]
589    #[serde(skip_serializing_if = "Option::is_none")]
590    pub sl_limit_price: Option<String>,
591}
592
593/// Body parameters for `POST /v5/order/amend-batch`.
594///
595/// # References
596/// - <https://bybit-exchange.github.io/docs/v5/order/batch-amend>
597#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
598#[serde(rename_all = "camelCase")]
599pub struct BybitBatchAmendOrderParams {
600    pub category: BybitProductType,
601    pub request: Vec<BybitBatchAmendOrderEntry>,
602}
603
604/// Body parameters for `POST /v5/order/amend`.
605///
606/// # References
607/// - <https://bybit-exchange.github.io/docs/v5/order/amend-order>
608#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
609#[serde(rename_all = "camelCase")]
610pub struct BybitAmendOrderParams {
611    #[serde(flatten)]
612    pub order: BybitBatchAmendOrderEntry,
613    pub category: BybitProductType,
614}
615
616/// Cancel entry for `POST /v5/order/cancel-batch`.
617///
618/// # References
619/// - <https://bybit-exchange.github.io/docs/v5/order/batch-cancel>
620#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
621#[serde(rename_all = "camelCase")]
622pub struct BybitBatchCancelOrderEntry {
623    pub symbol: String,
624    #[builder(default)]
625    #[serde(skip_serializing_if = "Option::is_none")]
626    #[builder(setter(strip_option))]
627    pub order_id: Option<String>,
628    #[builder(default)]
629    #[serde(skip_serializing_if = "Option::is_none")]
630    #[builder(setter(strip_option))]
631    pub order_link_id: Option<String>,
632    #[builder(default)]
633    #[serde(skip_serializing_if = "Option::is_none")]
634    pub order_filter: Option<BybitOrderFilter>,
635}
636
637/// Body parameters for `POST /v5/order/cancel-batch`.
638///
639/// # References
640/// - <https://bybit-exchange.github.io/docs/v5/order/batch-cancel>
641#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
642#[serde(rename_all = "camelCase")]
643pub struct BybitBatchCancelOrderParams {
644    pub category: BybitProductType,
645    pub request: Vec<BybitBatchCancelOrderEntry>,
646}
647
648/// Body parameters for `POST /v5/order/cancel`.
649///
650/// # References
651/// - <https://bybit-exchange.github.io/docs/v5/order/cancel-order>
652#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
653#[serde(rename_all = "camelCase")]
654pub struct BybitCancelOrderParams {
655    #[serde(flatten)]
656    pub order: BybitBatchCancelOrderEntry,
657    pub category: BybitProductType,
658}
659
660/// Body parameters for `POST /v5/order/cancel-all`.
661///
662/// # References
663/// - <https://bybit-exchange.github.io/docs/v5/order/cancel-all>
664#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
665#[serde(rename_all = "camelCase")]
666#[builder(default)]
667#[builder(setter(into))]
668pub struct BybitCancelAllOrdersParams {
669    pub category: BybitProductType,
670    #[serde(skip_serializing_if = "Option::is_none")]
671    #[builder(setter(strip_option))]
672    pub symbol: Option<String>,
673    #[serde(skip_serializing_if = "Option::is_none")]
674    #[builder(setter(strip_option))]
675    pub base_coin: Option<String>,
676    #[serde(skip_serializing_if = "Option::is_none")]
677    #[builder(setter(strip_option))]
678    pub settle_coin: Option<String>,
679    #[serde(skip_serializing_if = "Option::is_none")]
680    #[builder(setter(strip_option))]
681    pub order_filter: Option<BybitOrderFilter>,
682    #[serde(skip_serializing_if = "Option::is_none")]
683    #[builder(setter(strip_option))]
684    pub stop_order_type: Option<BybitStopOrderType>,
685}
686
687/// Query parameters for `GET /v5/order/realtime`.
688///
689/// # References
690/// - <https://bybit-exchange.github.io/docs/v5/order/open-order>
691#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
692#[serde(rename_all = "camelCase")]
693#[builder(default)]
694#[builder(setter(into))]
695pub struct BybitOpenOrdersParams {
696    pub category: BybitProductType,
697    #[serde(skip_serializing_if = "Option::is_none")]
698    #[builder(setter(strip_option))]
699    pub symbol: Option<String>,
700    #[serde(skip_serializing_if = "Option::is_none")]
701    #[builder(setter(strip_option))]
702    pub base_coin: Option<String>,
703    #[serde(skip_serializing_if = "Option::is_none")]
704    #[builder(setter(strip_option))]
705    pub settle_coin: Option<String>,
706    #[serde(skip_serializing_if = "Option::is_none")]
707    #[builder(setter(strip_option))]
708    pub order_id: Option<String>,
709    #[serde(skip_serializing_if = "Option::is_none")]
710    #[builder(setter(strip_option))]
711    pub order_link_id: Option<String>,
712    #[serde(skip_serializing_if = "Option::is_none")]
713    #[builder(setter(strip_option))]
714    pub open_only: Option<BybitOpenOnly>,
715    #[serde(skip_serializing_if = "Option::is_none")]
716    #[builder(setter(strip_option))]
717    pub order_filter: Option<BybitOrderFilter>,
718    #[serde(skip_serializing_if = "Option::is_none")]
719    #[builder(setter(strip_option))]
720    pub limit: Option<u32>,
721    #[serde(skip_serializing_if = "Option::is_none")]
722    #[builder(setter(strip_option))]
723    pub cursor: Option<String>,
724}
725
726/// Query parameters for `GET /v5/order/history`.
727///
728/// # References
729/// - <https://bybit-exchange.github.io/docs/v5/order/order-list>
730#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
731#[serde(rename_all = "camelCase")]
732#[builder(default)]
733#[builder(setter(into))]
734pub struct BybitOrderHistoryParams {
735    pub category: BybitProductType,
736    #[serde(skip_serializing_if = "Option::is_none")]
737    #[builder(setter(strip_option))]
738    pub symbol: Option<String>,
739    #[serde(skip_serializing_if = "Option::is_none")]
740    #[builder(setter(strip_option))]
741    pub base_coin: Option<String>,
742    #[serde(skip_serializing_if = "Option::is_none")]
743    #[builder(setter(strip_option))]
744    pub settle_coin: Option<String>,
745    #[serde(skip_serializing_if = "Option::is_none")]
746    #[builder(setter(strip_option))]
747    pub order_id: Option<String>,
748    #[serde(skip_serializing_if = "Option::is_none")]
749    #[builder(setter(strip_option))]
750    pub order_link_id: Option<String>,
751    #[serde(skip_serializing_if = "Option::is_none")]
752    #[builder(setter(strip_option))]
753    pub open_only: Option<BybitOpenOnly>,
754    #[serde(skip_serializing_if = "Option::is_none")]
755    #[builder(setter(strip_option))]
756    pub order_filter: Option<BybitOrderFilter>,
757    #[serde(skip_serializing_if = "Option::is_none")]
758    #[builder(setter(strip_option))]
759    pub order_status: Option<BybitOrderStatus>,
760    #[serde(skip_serializing_if = "Option::is_none")]
761    #[builder(setter(strip_option))]
762    pub start_time: Option<i64>,
763    #[serde(skip_serializing_if = "Option::is_none")]
764    #[serde(rename = "endTime")]
765    #[builder(setter(strip_option))]
766    pub end_time: Option<i64>,
767    #[serde(skip_serializing_if = "Option::is_none")]
768    #[builder(setter(strip_option))]
769    pub limit: Option<u32>,
770    #[serde(skip_serializing_if = "Option::is_none")]
771    #[builder(setter(strip_option))]
772    pub cursor: Option<String>,
773}
774
775/// Query parameters for `GET /v5/execution/list`.
776///
777/// # References
778/// - <https://bybit-exchange.github.io/docs/v5/order/execution>
779#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
780#[serde(rename_all = "camelCase")]
781#[builder(default)]
782#[builder(setter(into))]
783pub struct BybitTradeHistoryParams {
784    pub category: BybitProductType,
785    #[serde(skip_serializing_if = "Option::is_none")]
786    #[builder(setter(strip_option))]
787    pub symbol: Option<String>,
788    #[serde(skip_serializing_if = "Option::is_none")]
789    #[builder(setter(strip_option))]
790    pub base_coin: Option<String>,
791    #[serde(skip_serializing_if = "Option::is_none")]
792    #[builder(setter(strip_option))]
793    pub order_id: Option<String>,
794    #[serde(skip_serializing_if = "Option::is_none")]
795    #[builder(setter(strip_option))]
796    pub order_link_id: Option<String>,
797    #[serde(skip_serializing_if = "Option::is_none")]
798    #[builder(setter(strip_option))]
799    pub start_time: Option<i64>,
800    #[serde(skip_serializing_if = "Option::is_none")]
801    #[serde(rename = "endTime")]
802    #[builder(setter(strip_option))]
803    pub end_time: Option<i64>,
804    #[serde(skip_serializing_if = "Option::is_none")]
805    #[builder(setter(strip_option))]
806    pub exec_type: Option<BybitExecType>,
807    #[serde(skip_serializing_if = "Option::is_none")]
808    #[builder(setter(strip_option))]
809    pub limit: Option<u32>,
810    #[serde(skip_serializing_if = "Option::is_none")]
811    #[builder(setter(strip_option))]
812    pub cursor: Option<String>,
813}
814
815/// Request-side permission patch for API-key update endpoints (`PascalCase`).
816///
817/// Kept separate from the response-side
818/// [`crate::http::models::BybitApiKeyPermissions`]: every field here is
819/// `Option<Vec<String>>` with `skip_serializing_if`, so an unset bucket is
820/// omitted from the request body entirely rather than being serialised as an
821/// explicit empty array (which the venue treats as "clear all permissions").
822///
823/// The field set is the superset of the master and sub-account permission
824/// tables. Buckets that only appear for master-account keys (`FiatP2P`,
825/// `FiatBybitPay`, `FiatBitPay`, `FiatConvertBroker`, `BitCard`, `ByXPost`)
826/// must not be set when calling `POST /v5/user/update-sub-api`; callers are
827/// expected to only populate the buckets documented for the endpoint being
828/// invoked.
829///
830/// # References
831/// - <https://bybit-exchange.github.io/docs/v5/user/modify-sub-apikey>
832/// - <https://bybit-exchange.github.io/docs/v5/user/modify-master-apikey>
833#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
834#[serde(rename_all = "PascalCase")]
835#[builder(default)]
836#[builder(setter(into, strip_option))]
837pub struct BybitApiKeyPermissionUpdate {
838    #[serde(skip_serializing_if = "Option::is_none")]
839    pub contract_trade: Option<Vec<String>>,
840    #[serde(skip_serializing_if = "Option::is_none")]
841    pub spot: Option<Vec<String>>,
842    #[serde(skip_serializing_if = "Option::is_none")]
843    pub wallet: Option<Vec<String>>,
844    #[serde(skip_serializing_if = "Option::is_none")]
845    pub options: Option<Vec<String>>,
846    #[serde(skip_serializing_if = "Option::is_none")]
847    pub derivatives: Option<Vec<String>>,
848    #[serde(skip_serializing_if = "Option::is_none")]
849    pub exchange: Option<Vec<String>>,
850    #[serde(skip_serializing_if = "Option::is_none")]
851    pub earn: Option<Vec<String>>,
852    #[serde(skip_serializing_if = "Option::is_none")]
853    pub affiliate: Option<Vec<String>>,
854    #[serde(skip_serializing_if = "Option::is_none")]
855    pub block_trade: Option<Vec<String>>,
856    #[serde(skip_serializing_if = "Option::is_none")]
857    pub copy_trading: Option<Vec<String>>,
858    // Bybit ships this key uppercase (`"NFT"`); the struct-level PascalCase
859    // rule would otherwise emit `"Nft"` and the venue would ignore the field.
860    #[serde(rename = "NFT", skip_serializing_if = "Option::is_none")]
861    pub nft: Option<Vec<String>>,
862    // Bybit uses `"FiatP2P"` — PascalCase rename would emit `"FiatP2p"`.
863    #[serde(rename = "FiatP2P", skip_serializing_if = "Option::is_none")]
864    pub fiat_p2p: Option<Vec<String>>,
865    #[serde(skip_serializing_if = "Option::is_none")]
866    pub fiat_bybit_pay: Option<Vec<String>>,
867    #[serde(skip_serializing_if = "Option::is_none")]
868    pub fiat_bit_pay: Option<Vec<String>>,
869    #[serde(skip_serializing_if = "Option::is_none")]
870    pub fiat_convert_broker: Option<Vec<String>>,
871    #[serde(skip_serializing_if = "Option::is_none")]
872    pub bit_card: Option<Vec<String>>,
873    // Bybit uses `"ByXPost"` — PascalCase rename would emit `"ByxPost"`.
874    #[serde(rename = "ByXPost", skip_serializing_if = "Option::is_none")]
875    pub byx_post: Option<Vec<String>>,
876}
877
878/// Body parameters for `POST /v5/user/update-sub-api`.
879///
880/// `api_key` is only required when a master key is editing a sub-account key;
881/// it is omitted when a sub key edits itself.
882///
883/// # References
884/// - <https://bybit-exchange.github.io/docs/v5/user/modify-sub-apikey>
885#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
886#[serde(rename_all = "camelCase")]
887#[builder(default)]
888#[builder(setter(into))]
889pub struct BybitUpdateSubApiParams {
890    #[serde(skip_serializing_if = "Option::is_none")]
891    #[builder(setter(strip_option))]
892    pub api_key: Option<String>,
893    // Bybit accepts `readOnly` as a 0/1 integer on the wire; the builder takes
894    // a `bool` and `opt_bool_as_int` serialises it to match.
895    #[serde(skip_serializing_if = "Option::is_none", with = "opt_bool_as_int")]
896    #[builder(setter(strip_option))]
897    pub read_only: Option<bool>,
898    #[serde(skip_serializing_if = "Option::is_none")]
899    #[builder(setter(strip_option))]
900    pub ips: Option<String>,
901    #[serde(skip_serializing_if = "Option::is_none")]
902    #[builder(setter(strip_option))]
903    pub permissions: Option<BybitApiKeyPermissionUpdate>,
904}
905
906/// Body parameters for `POST /v5/user/update-api`.
907///
908/// Can only modify the caller's own master key.
909///
910/// The official request parameter table lists only `readOnly` and
911/// `permissions`; `ips` appears in the response but is not a request field,
912/// so it is deliberately excluded here.
913///
914/// # References
915/// - <https://bybit-exchange.github.io/docs/v5/user/modify-master-apikey>
916#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
917#[serde(rename_all = "camelCase")]
918#[builder(default)]
919#[builder(setter(into))]
920pub struct BybitUpdateMasterApiParams {
921    #[serde(skip_serializing_if = "Option::is_none", with = "opt_bool_as_int")]
922    #[builder(setter(strip_option))]
923    pub read_only: Option<bool>,
924    #[serde(skip_serializing_if = "Option::is_none")]
925    #[builder(setter(strip_option))]
926    pub permissions: Option<BybitApiKeyPermissionUpdate>,
927}
928
929/// Query parameters for cursor-paginated sub-account listings.
930///
931/// Shared by `GET /v5/user/submembers` and `GET /v5/user/escrow_sub_members`,
932/// which take the same pagination shape (`pageSize` up to 100 plus
933/// `nextCursor`). Bybit documents `pageSize` as a string, but the URL encoder
934/// serialises any numeric value as text anyway, so `u32` is used on the Rust
935/// side for compile-time type safety.
936///
937/// # References
938/// - <https://bybit-exchange.github.io/docs/v5/user/page-subuid>
939/// - <https://bybit-exchange.github.io/docs/v5/user/fund-subuid-list>
940#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
941#[serde(rename_all = "camelCase")]
942#[builder(default)]
943#[builder(setter(into))]
944pub struct BybitSubMembersPageParams {
945    #[serde(skip_serializing_if = "Option::is_none")]
946    #[builder(setter(strip_option))]
947    pub page_size: Option<u32>,
948    #[serde(skip_serializing_if = "Option::is_none")]
949    #[builder(setter(strip_option))]
950    pub next_cursor: Option<String>,
951}
952
953/// Query parameters for `GET /v5/user/sub-apikeys`.
954///
955/// `sub_member_id` is required, so this struct intentionally does not carry
956/// `#[builder(default)]`; the generated builder returns `Err` when the field
957/// is missing, matching the convention used by `BybitBorrowParams`.
958///
959/// # References
960/// - <https://bybit-exchange.github.io/docs/v5/user/list-sub-apikeys>
961#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
962#[serde(rename_all = "camelCase")]
963#[builder(setter(into))]
964pub struct BybitSubApiKeysParams {
965    pub sub_member_id: String,
966    #[serde(skip_serializing_if = "Option::is_none")]
967    #[builder(setter(strip_option), default)]
968    pub limit: Option<u32>,
969    #[serde(skip_serializing_if = "Option::is_none")]
970    #[builder(setter(strip_option), default)]
971    pub cursor: Option<String>,
972}
973
974#[cfg(test)]
975mod tests {
976    use rstest::rstest;
977
978    use super::*;
979
980    #[rstest]
981    fn test_amend_entry_builds_with_only_order_id() {
982        let entry = BybitBatchAmendOrderEntryBuilder::default()
983            .symbol("ETHUSDT".to_string())
984            .order_id("12345".to_string())
985            .price(Some("1600.0".to_string()))
986            .build()
987            .expect("amend entry should build with only order_id");
988
989        assert_eq!(entry.order_id.as_deref(), Some("12345"));
990        assert_eq!(entry.order_link_id, None);
991    }
992
993    #[rstest]
994    fn test_amend_entry_builds_with_only_order_link_id() {
995        let entry = BybitBatchAmendOrderEntryBuilder::default()
996            .symbol("ETHUSDT".to_string())
997            .order_link_id("link-1".to_string())
998            .qty(Some("0.02".to_string()))
999            .build()
1000            .expect("amend entry should build with only order_link_id");
1001
1002        assert_eq!(entry.order_link_id.as_deref(), Some("link-1"));
1003        assert_eq!(entry.order_id, None);
1004    }
1005
1006    #[rstest]
1007    fn test_amend_entry_builds_with_no_identifiers() {
1008        let entry = BybitBatchAmendOrderEntryBuilder::default()
1009            .symbol("ETHUSDT".to_string())
1010            .price(Some("1600.0".to_string()))
1011            .build()
1012            .expect("amend entry optional identifiers default to None");
1013
1014        assert_eq!(entry.order_id, None);
1015        assert_eq!(entry.order_link_id, None);
1016    }
1017
1018    #[rstest]
1019    fn test_cancel_entry_builds_with_only_order_id() {
1020        let entry = BybitBatchCancelOrderEntryBuilder::default()
1021            .symbol("ETHUSDT".to_string())
1022            .order_id("12345".to_string())
1023            .build()
1024            .expect("cancel entry should build with only order_id");
1025
1026        assert_eq!(entry.order_id.as_deref(), Some("12345"));
1027        assert_eq!(entry.order_link_id, None);
1028    }
1029
1030    #[rstest]
1031    fn test_cancel_entry_builds_with_only_order_link_id() {
1032        let entry = BybitBatchCancelOrderEntryBuilder::default()
1033            .symbol("ETHUSDT".to_string())
1034            .order_link_id("link-1".to_string())
1035            .build()
1036            .expect("cancel entry should build with only order_link_id");
1037
1038        assert_eq!(entry.order_link_id.as_deref(), Some("link-1"));
1039        assert_eq!(entry.order_id, None);
1040    }
1041}