Skip to main content

nautilus_binance/spot/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//! Query parameter builders for Binance Spot HTTP requests.
17
18use serde::Serialize;
19
20use crate::{
21    common::enums::{BinanceSelfTradePreventionMode, BinanceSide, BinanceTimeInForce},
22    spot::enums::{BinanceCancelReplaceMode, BinanceOrderResponseType, BinanceSpotOrderType},
23};
24
25/// Query parameters for the depth endpoint.
26#[derive(Debug, Clone, Serialize)]
27pub struct DepthParams {
28    /// Trading pair symbol (e.g., "BTCUSDT").
29    pub symbol: String,
30    /// Number of price levels to return (default 100, max 5000).
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub limit: Option<u32>,
33}
34
35impl DepthParams {
36    /// Create new depth query params.
37    #[must_use]
38    pub fn new(symbol: impl Into<String>) -> Self {
39        Self {
40            symbol: symbol.into(),
41            limit: None,
42        }
43    }
44
45    /// Set the limit.
46    #[must_use]
47    pub fn with_limit(mut self, limit: u32) -> Self {
48        self.limit = Some(limit);
49        self
50    }
51}
52
53/// Query parameters for the trades endpoint.
54#[derive(Debug, Clone, Serialize)]
55pub struct TradesParams {
56    /// Trading pair symbol (e.g., "BTCUSDT").
57    pub symbol: String,
58    /// Number of trades to return (default 500, max 1000).
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub limit: Option<u32>,
61}
62
63impl TradesParams {
64    /// Create new trades query params.
65    #[must_use]
66    pub fn new(symbol: impl Into<String>) -> Self {
67        Self {
68            symbol: symbol.into(),
69            limit: None,
70        }
71    }
72
73    /// Set the limit.
74    #[must_use]
75    pub fn with_limit(mut self, limit: u32) -> Self {
76        self.limit = Some(limit);
77        self
78    }
79}
80
81/// Query parameters for new order submission.
82#[derive(Debug, Clone, Serialize)]
83pub struct NewOrderParams {
84    /// Trading pair symbol.
85    pub symbol: String,
86    /// Order side (BUY or SELL).
87    pub side: BinanceSide,
88    /// Order type.
89    #[serde(rename = "type")]
90    pub order_type: BinanceSpotOrderType,
91    /// Time in force.
92    #[serde(skip_serializing_if = "Option::is_none", rename = "timeInForce")]
93    pub time_in_force: Option<BinanceTimeInForce>,
94    /// Order quantity.
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub quantity: Option<String>,
97    /// Quote order quantity (for market orders).
98    #[serde(skip_serializing_if = "Option::is_none", rename = "quoteOrderQty")]
99    pub quote_order_qty: Option<String>,
100    /// Limit price.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub price: Option<String>,
103    /// Client order ID.
104    #[serde(skip_serializing_if = "Option::is_none", rename = "newClientOrderId")]
105    pub new_client_order_id: Option<String>,
106    /// Stop price for stop orders.
107    #[serde(skip_serializing_if = "Option::is_none", rename = "stopPrice")]
108    pub stop_price: Option<String>,
109    /// Trailing delta for trailing stop orders.
110    #[serde(skip_serializing_if = "Option::is_none", rename = "trailingDelta")]
111    pub trailing_delta: Option<i64>,
112    /// Iceberg quantity.
113    #[serde(skip_serializing_if = "Option::is_none", rename = "icebergQty")]
114    pub iceberg_qty: Option<String>,
115    /// Response type (ACK, RESULT, or FULL).
116    #[serde(skip_serializing_if = "Option::is_none", rename = "newOrderRespType")]
117    pub new_order_resp_type: Option<BinanceOrderResponseType>,
118    /// Self-trade prevention mode.
119    #[serde(
120        skip_serializing_if = "Option::is_none",
121        rename = "selfTradePreventionMode"
122    )]
123    pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
124    /// Strategy ID for order tracking.
125    #[serde(skip_serializing_if = "Option::is_none", rename = "strategyId")]
126    pub strategy_id: Option<i64>,
127    /// Strategy type for order tracking.
128    #[serde(skip_serializing_if = "Option::is_none", rename = "strategyType")]
129    pub strategy_type: Option<i64>,
130}
131
132impl NewOrderParams {
133    /// Create new order params for a limit order.
134    #[must_use]
135    pub fn limit(
136        symbol: impl Into<String>,
137        side: BinanceSide,
138        quantity: impl Into<String>,
139        price: impl Into<String>,
140    ) -> Self {
141        Self {
142            symbol: symbol.into(),
143            side,
144            order_type: BinanceSpotOrderType::Limit,
145            time_in_force: Some(BinanceTimeInForce::Gtc),
146            quantity: Some(quantity.into()),
147            quote_order_qty: None,
148            price: Some(price.into()),
149            new_client_order_id: None,
150            stop_price: None,
151            trailing_delta: None,
152            iceberg_qty: None,
153            new_order_resp_type: Some(BinanceOrderResponseType::Full),
154            self_trade_prevention_mode: None,
155            strategy_id: None,
156            strategy_type: None,
157        }
158    }
159
160    /// Create new order params for a market order.
161    #[must_use]
162    pub fn market(
163        symbol: impl Into<String>,
164        side: BinanceSide,
165        quantity: impl Into<String>,
166    ) -> Self {
167        Self {
168            symbol: symbol.into(),
169            side,
170            order_type: BinanceSpotOrderType::Market,
171            time_in_force: None,
172            quantity: Some(quantity.into()),
173            quote_order_qty: None,
174            price: None,
175            new_client_order_id: None,
176            stop_price: None,
177            trailing_delta: None,
178            iceberg_qty: None,
179            new_order_resp_type: Some(BinanceOrderResponseType::Full),
180            self_trade_prevention_mode: None,
181            strategy_id: None,
182            strategy_type: None,
183        }
184    }
185
186    /// Set the client order ID.
187    #[must_use]
188    pub fn with_client_order_id(mut self, id: impl Into<String>) -> Self {
189        self.new_client_order_id = Some(id.into());
190        self
191    }
192
193    /// Set the time in force.
194    #[must_use]
195    pub fn with_time_in_force(mut self, tif: BinanceTimeInForce) -> Self {
196        self.time_in_force = Some(tif);
197        self
198    }
199
200    /// Set the stop price.
201    #[must_use]
202    pub fn with_stop_price(mut self, price: impl Into<String>) -> Self {
203        self.stop_price = Some(price.into());
204        self
205    }
206
207    /// Set the self-trade prevention mode.
208    #[must_use]
209    pub fn with_stp_mode(mut self, mode: BinanceSelfTradePreventionMode) -> Self {
210        self.self_trade_prevention_mode = Some(mode);
211        self
212    }
213}
214
215/// Query parameters for canceling an order.
216#[derive(Debug, Clone, Serialize)]
217pub struct CancelOrderParams {
218    /// Trading pair symbol.
219    pub symbol: String,
220    /// Order ID to cancel.
221    #[serde(skip_serializing_if = "Option::is_none", rename = "orderId")]
222    pub order_id: Option<i64>,
223    /// Original client order ID.
224    #[serde(skip_serializing_if = "Option::is_none", rename = "origClientOrderId")]
225    pub orig_client_order_id: Option<String>,
226    /// New client order ID for the cancel request.
227    #[serde(skip_serializing_if = "Option::is_none", rename = "newClientOrderId")]
228    pub new_client_order_id: Option<String>,
229}
230
231impl CancelOrderParams {
232    /// Create cancel params by order ID.
233    #[must_use]
234    pub fn by_order_id(symbol: impl Into<String>, order_id: i64) -> Self {
235        Self {
236            symbol: symbol.into(),
237            order_id: Some(order_id),
238            orig_client_order_id: None,
239            new_client_order_id: None,
240        }
241    }
242
243    /// Create cancel params by client order ID.
244    #[must_use]
245    pub fn by_client_order_id(
246        symbol: impl Into<String>,
247        client_order_id: impl Into<String>,
248    ) -> Self {
249        Self {
250            symbol: symbol.into(),
251            order_id: None,
252            orig_client_order_id: Some(client_order_id.into()),
253            new_client_order_id: None,
254        }
255    }
256}
257
258/// Query parameters for canceling all open orders on a symbol.
259#[derive(Debug, Clone, Serialize)]
260pub struct CancelOpenOrdersParams {
261    /// Trading pair symbol.
262    pub symbol: String,
263}
264
265impl CancelOpenOrdersParams {
266    /// Create new cancel open orders params.
267    #[must_use]
268    pub fn new(symbol: impl Into<String>) -> Self {
269        Self {
270            symbol: symbol.into(),
271        }
272    }
273}
274
275/// Query parameters for cancel and replace order.
276#[derive(Debug, Clone, Serialize)]
277pub struct CancelReplaceOrderParams {
278    /// Trading pair symbol.
279    pub symbol: String,
280    /// Order side.
281    pub side: BinanceSide,
282    /// Order type.
283    #[serde(rename = "type")]
284    pub order_type: BinanceSpotOrderType,
285    /// Cancel/replace mode.
286    #[serde(rename = "cancelReplaceMode")]
287    pub cancel_replace_mode: BinanceCancelReplaceMode,
288    /// Time in force.
289    #[serde(skip_serializing_if = "Option::is_none", rename = "timeInForce")]
290    pub time_in_force: Option<BinanceTimeInForce>,
291    /// Order quantity.
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub quantity: Option<String>,
294    /// Quote order quantity.
295    #[serde(skip_serializing_if = "Option::is_none", rename = "quoteOrderQty")]
296    pub quote_order_qty: Option<String>,
297    /// Limit price.
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub price: Option<String>,
300    /// Order ID to cancel.
301    #[serde(skip_serializing_if = "Option::is_none", rename = "cancelOrderId")]
302    pub cancel_order_id: Option<i64>,
303    /// Client order ID to cancel.
304    #[serde(
305        skip_serializing_if = "Option::is_none",
306        rename = "cancelOrigClientOrderId"
307    )]
308    pub cancel_orig_client_order_id: Option<String>,
309    /// New client order ID.
310    #[serde(skip_serializing_if = "Option::is_none", rename = "newClientOrderId")]
311    pub new_client_order_id: Option<String>,
312    /// Stop price.
313    #[serde(skip_serializing_if = "Option::is_none", rename = "stopPrice")]
314    pub stop_price: Option<String>,
315    /// Trailing delta.
316    #[serde(skip_serializing_if = "Option::is_none", rename = "trailingDelta")]
317    pub trailing_delta: Option<i64>,
318    /// Iceberg quantity.
319    #[serde(skip_serializing_if = "Option::is_none", rename = "icebergQty")]
320    pub iceberg_qty: Option<String>,
321    /// Response type.
322    #[serde(skip_serializing_if = "Option::is_none", rename = "newOrderRespType")]
323    pub new_order_resp_type: Option<BinanceOrderResponseType>,
324    /// Self-trade prevention mode.
325    #[serde(
326        skip_serializing_if = "Option::is_none",
327        rename = "selfTradePreventionMode"
328    )]
329    pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
330}
331
332/// Query parameters for querying a single order.
333#[derive(Debug, Clone, Serialize)]
334pub struct QueryOrderParams {
335    /// Trading pair symbol.
336    pub symbol: String,
337    /// Order ID.
338    #[serde(skip_serializing_if = "Option::is_none", rename = "orderId")]
339    pub order_id: Option<i64>,
340    /// Original client order ID.
341    #[serde(skip_serializing_if = "Option::is_none", rename = "origClientOrderId")]
342    pub orig_client_order_id: Option<String>,
343}
344
345impl QueryOrderParams {
346    /// Create query params by order ID.
347    #[must_use]
348    pub fn by_order_id(symbol: impl Into<String>, order_id: i64) -> Self {
349        Self {
350            symbol: symbol.into(),
351            order_id: Some(order_id),
352            orig_client_order_id: None,
353        }
354    }
355
356    /// Create query params by client order ID.
357    #[must_use]
358    pub fn by_client_order_id(
359        symbol: impl Into<String>,
360        client_order_id: impl Into<String>,
361    ) -> Self {
362        Self {
363            symbol: symbol.into(),
364            order_id: None,
365            orig_client_order_id: Some(client_order_id.into()),
366        }
367    }
368}
369
370/// Query parameters for querying open orders.
371#[derive(Debug, Clone, Default, Serialize)]
372pub struct OpenOrdersParams {
373    /// Trading pair symbol (optional, omit for all symbols).
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub symbol: Option<String>,
376}
377
378impl OpenOrdersParams {
379    /// Create new open orders params for all symbols.
380    #[must_use]
381    pub fn all() -> Self {
382        Self { symbol: None }
383    }
384
385    /// Create new open orders params for a specific symbol.
386    #[must_use]
387    pub fn for_symbol(symbol: impl Into<String>) -> Self {
388        Self {
389            symbol: Some(symbol.into()),
390        }
391    }
392}
393
394/// Query parameters for querying all orders (includes filled/canceled).
395#[derive(Debug, Clone, Serialize)]
396pub struct AllOrdersParams {
397    /// Trading pair symbol.
398    pub symbol: String,
399    /// Filter by order ID (returns orders >= this ID).
400    #[serde(skip_serializing_if = "Option::is_none", rename = "orderId")]
401    pub order_id: Option<i64>,
402    /// Filter by start time.
403    #[serde(skip_serializing_if = "Option::is_none", rename = "startTime")]
404    pub start_time: Option<i64>,
405    /// Filter by end time.
406    #[serde(skip_serializing_if = "Option::is_none", rename = "endTime")]
407    pub end_time: Option<i64>,
408    /// Maximum number of orders to return (default 500, max 1000).
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub limit: Option<u32>,
411}
412
413impl AllOrdersParams {
414    /// Create new all orders params.
415    #[must_use]
416    pub fn new(symbol: impl Into<String>) -> Self {
417        Self {
418            symbol: symbol.into(),
419            order_id: None,
420            start_time: None,
421            end_time: None,
422            limit: None,
423        }
424    }
425
426    /// Set the limit.
427    #[must_use]
428    pub fn with_limit(mut self, limit: u32) -> Self {
429        self.limit = Some(limit);
430        self
431    }
432
433    /// Set the time range.
434    #[must_use]
435    pub fn with_time_range(mut self, start: i64, end: i64) -> Self {
436        self.start_time = Some(start);
437        self.end_time = Some(end);
438        self
439    }
440}
441
442/// Query parameters for new OCO order.
443#[derive(Debug, Clone, Serialize)]
444pub struct NewOcoOrderParams {
445    /// Trading pair symbol.
446    pub symbol: String,
447    /// Order side.
448    pub side: BinanceSide,
449    /// Order quantity.
450    pub quantity: String,
451    /// Limit price (above-market for sell, below-market for buy).
452    pub price: String,
453    /// Stop price trigger.
454    #[serde(rename = "stopPrice")]
455    pub stop_price: String,
456    /// Stop limit price (optional, creates stop-limit if provided).
457    #[serde(skip_serializing_if = "Option::is_none", rename = "stopLimitPrice")]
458    pub stop_limit_price: Option<String>,
459    /// Client order ID for the entire list.
460    #[serde(skip_serializing_if = "Option::is_none", rename = "listClientOrderId")]
461    pub list_client_order_id: Option<String>,
462    /// Client order ID for the limit order.
463    #[serde(skip_serializing_if = "Option::is_none", rename = "limitClientOrderId")]
464    pub limit_client_order_id: Option<String>,
465    /// Client order ID for the stop order.
466    #[serde(skip_serializing_if = "Option::is_none", rename = "stopClientOrderId")]
467    pub stop_client_order_id: Option<String>,
468    /// Iceberg quantity for the limit leg.
469    #[serde(skip_serializing_if = "Option::is_none", rename = "limitIcebergQty")]
470    pub limit_iceberg_qty: Option<String>,
471    /// Iceberg quantity for the stop leg.
472    #[serde(skip_serializing_if = "Option::is_none", rename = "stopIcebergQty")]
473    pub stop_iceberg_qty: Option<String>,
474    /// Time in force for the stop-limit leg.
475    #[serde(
476        skip_serializing_if = "Option::is_none",
477        rename = "stopLimitTimeInForce"
478    )]
479    pub stop_limit_time_in_force: Option<BinanceTimeInForce>,
480    /// Response type.
481    #[serde(skip_serializing_if = "Option::is_none", rename = "newOrderRespType")]
482    pub new_order_resp_type: Option<BinanceOrderResponseType>,
483    /// Self-trade prevention mode.
484    #[serde(
485        skip_serializing_if = "Option::is_none",
486        rename = "selfTradePreventionMode"
487    )]
488    pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
489}
490
491impl NewOcoOrderParams {
492    /// Create new OCO order params.
493    #[must_use]
494    pub fn new(
495        symbol: impl Into<String>,
496        side: BinanceSide,
497        quantity: impl Into<String>,
498        price: impl Into<String>,
499        stop_price: impl Into<String>,
500    ) -> Self {
501        Self {
502            symbol: symbol.into(),
503            side,
504            quantity: quantity.into(),
505            price: price.into(),
506            stop_price: stop_price.into(),
507            stop_limit_price: None,
508            list_client_order_id: None,
509            limit_client_order_id: None,
510            stop_client_order_id: None,
511            limit_iceberg_qty: None,
512            stop_iceberg_qty: None,
513            stop_limit_time_in_force: None,
514            new_order_resp_type: Some(BinanceOrderResponseType::Full),
515            self_trade_prevention_mode: None,
516        }
517    }
518
519    /// Set stop limit price (makes stop leg a stop-limit order).
520    #[must_use]
521    pub fn with_stop_limit_price(mut self, price: impl Into<String>) -> Self {
522        self.stop_limit_price = Some(price.into());
523        self.stop_limit_time_in_force = Some(BinanceTimeInForce::Gtc);
524        self
525    }
526}
527
528/// Query parameters for canceling an order list (OCO).
529#[derive(Debug, Clone, Serialize)]
530pub struct CancelOrderListParams {
531    /// Trading pair symbol.
532    pub symbol: String,
533    /// Order list ID.
534    #[serde(skip_serializing_if = "Option::is_none", rename = "orderListId")]
535    pub order_list_id: Option<i64>,
536    /// List client order ID.
537    #[serde(skip_serializing_if = "Option::is_none", rename = "listClientOrderId")]
538    pub list_client_order_id: Option<String>,
539    /// New client order ID for the cancel request.
540    #[serde(skip_serializing_if = "Option::is_none", rename = "newClientOrderId")]
541    pub new_client_order_id: Option<String>,
542}
543
544impl CancelOrderListParams {
545    /// Create cancel params by order list ID.
546    #[must_use]
547    pub fn by_order_list_id(symbol: impl Into<String>, order_list_id: i64) -> Self {
548        Self {
549            symbol: symbol.into(),
550            order_list_id: Some(order_list_id),
551            list_client_order_id: None,
552            new_client_order_id: None,
553        }
554    }
555
556    /// Create cancel params by list client order ID.
557    #[must_use]
558    pub fn by_list_client_order_id(
559        symbol: impl Into<String>,
560        list_client_order_id: impl Into<String>,
561    ) -> Self {
562        Self {
563            symbol: symbol.into(),
564            order_list_id: None,
565            list_client_order_id: Some(list_client_order_id.into()),
566            new_client_order_id: None,
567        }
568    }
569}
570
571/// Query parameters for querying an order list (OCO).
572#[derive(Debug, Clone, Serialize)]
573pub struct QueryOrderListParams {
574    /// Order list ID.
575    #[serde(skip_serializing_if = "Option::is_none", rename = "orderListId")]
576    pub order_list_id: Option<i64>,
577    /// List client order ID.
578    #[serde(skip_serializing_if = "Option::is_none", rename = "origClientOrderId")]
579    pub orig_client_order_id: Option<String>,
580}
581
582impl QueryOrderListParams {
583    /// Create query params by order list ID.
584    #[must_use]
585    pub fn by_order_list_id(order_list_id: i64) -> Self {
586        Self {
587            order_list_id: Some(order_list_id),
588            orig_client_order_id: None,
589        }
590    }
591
592    /// Create query params by list client order ID.
593    #[must_use]
594    pub fn by_client_order_id(client_order_id: impl Into<String>) -> Self {
595        Self {
596            order_list_id: None,
597            orig_client_order_id: Some(client_order_id.into()),
598        }
599    }
600}
601
602/// Query parameters for querying all order lists (OCOs).
603#[derive(Debug, Clone, Default, Serialize)]
604pub struct AllOrderListsParams {
605    /// Filter by start time.
606    #[serde(skip_serializing_if = "Option::is_none", rename = "startTime")]
607    pub start_time: Option<i64>,
608    /// Filter by end time.
609    #[serde(skip_serializing_if = "Option::is_none", rename = "endTime")]
610    pub end_time: Option<i64>,
611    /// Maximum number of results (default 500, max 1000).
612    #[serde(skip_serializing_if = "Option::is_none")]
613    pub limit: Option<u32>,
614}
615
616/// Query parameters for querying open order lists (OCOs).
617#[derive(Debug, Clone, Default, Serialize)]
618pub struct OpenOrderListsParams {}
619
620/// Query parameters for account information.
621#[derive(Debug, Clone, Default, Serialize)]
622pub struct AccountInfoParams {
623    /// Omit zero balances from response.
624    #[serde(skip_serializing_if = "Option::is_none", rename = "omitZeroBalances")]
625    pub omit_zero_balances: Option<bool>,
626}
627
628impl AccountInfoParams {
629    /// Create new account info params.
630    #[must_use]
631    pub fn new() -> Self {
632        Self::default()
633    }
634
635    /// Omit zero balances from response.
636    #[must_use]
637    pub fn omit_zero_balances(mut self) -> Self {
638        self.omit_zero_balances = Some(true);
639        self
640    }
641}
642
643/// Query parameters for account trades.
644#[derive(Debug, Clone, Serialize)]
645pub struct AccountTradesParams {
646    /// Trading pair symbol.
647    pub symbol: String,
648    /// Filter by order ID.
649    #[serde(skip_serializing_if = "Option::is_none", rename = "orderId")]
650    pub order_id: Option<i64>,
651    /// Filter by start time.
652    #[serde(skip_serializing_if = "Option::is_none", rename = "startTime")]
653    pub start_time: Option<i64>,
654    /// Filter by end time.
655    #[serde(skip_serializing_if = "Option::is_none", rename = "endTime")]
656    pub end_time: Option<i64>,
657    /// Filter by trade ID (returns trades >= this ID).
658    #[serde(skip_serializing_if = "Option::is_none", rename = "fromId")]
659    pub from_id: Option<i64>,
660    /// Maximum number of trades to return (default 500, max 1000).
661    #[serde(skip_serializing_if = "Option::is_none")]
662    pub limit: Option<u32>,
663}
664
665impl AccountTradesParams {
666    /// Create new account trades params.
667    #[must_use]
668    pub fn new(symbol: impl Into<String>) -> Self {
669        Self {
670            symbol: symbol.into(),
671            order_id: None,
672            start_time: None,
673            end_time: None,
674            from_id: None,
675            limit: None,
676        }
677    }
678
679    /// Filter by order ID.
680    #[must_use]
681    pub fn for_order(mut self, order_id: i64) -> Self {
682        self.order_id = Some(order_id);
683        self
684    }
685
686    /// Set the limit.
687    #[must_use]
688    pub fn with_limit(mut self, limit: u32) -> Self {
689        self.limit = Some(limit);
690        self
691    }
692
693    /// Set the time range.
694    #[must_use]
695    pub fn with_time_range(mut self, start: i64, end: i64) -> Self {
696        self.start_time = Some(start);
697        self.end_time = Some(end);
698        self
699    }
700}
701
702/// Query parameters for klines (candlestick) data.
703#[derive(Debug, Clone, Serialize)]
704pub struct KlinesParams {
705    /// Trading pair symbol (e.g., "BTCUSDT").
706    pub symbol: String,
707    /// Kline interval (e.g., "1m", "1h", "1d").
708    pub interval: String,
709    /// Filter by start time (milliseconds).
710    #[serde(skip_serializing_if = "Option::is_none", rename = "startTime")]
711    pub start_time: Option<i64>,
712    /// Filter by end time (milliseconds).
713    #[serde(skip_serializing_if = "Option::is_none", rename = "endTime")]
714    pub end_time: Option<i64>,
715    /// Kline time zone offset (+/- hours, default 0 UTC).
716    #[serde(skip_serializing_if = "Option::is_none", rename = "timeZone")]
717    pub time_zone: Option<String>,
718    /// Maximum number of klines to return (default 500, max 1000).
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub limit: Option<u32>,
721}
722
723/// Query parameters for listen key operations (extend/close).
724#[derive(Debug, Clone, Serialize)]
725pub struct ListenKeyParams {
726    /// The listen key to extend or close.
727    #[serde(rename = "listenKey")]
728    pub listen_key: String,
729}
730
731impl ListenKeyParams {
732    /// Creates new listen key params.
733    #[must_use]
734    pub fn new(listen_key: impl Into<String>) -> Self {
735        Self {
736            listen_key: listen_key.into(),
737        }
738    }
739}
740
741/// Query parameters for ticker endpoints.
742#[derive(Debug, Clone, Default, Serialize)]
743pub struct TickerParams {
744    /// Trading pair symbol (optional, omit for all symbols).
745    #[serde(skip_serializing_if = "Option::is_none")]
746    pub symbol: Option<String>,
747}
748
749impl TickerParams {
750    /// Creates ticker params for all symbols.
751    #[must_use]
752    pub fn all() -> Self {
753        Self { symbol: None }
754    }
755
756    /// Creates ticker params for a specific symbol.
757    #[must_use]
758    pub fn for_symbol(symbol: impl Into<String>) -> Self {
759        Self {
760            symbol: Some(symbol.into()),
761        }
762    }
763}
764
765/// Query parameters for average price endpoint.
766#[derive(Debug, Clone, Serialize)]
767pub struct AvgPriceParams {
768    /// Trading pair symbol (required).
769    pub symbol: String,
770}
771
772impl AvgPriceParams {
773    /// Creates average price params.
774    #[must_use]
775    pub fn new(symbol: impl Into<String>) -> Self {
776        Self {
777            symbol: symbol.into(),
778        }
779    }
780}
781
782/// Query parameters for trade fee endpoint.
783#[derive(Debug, Clone, Default, Serialize)]
784pub struct TradeFeeParams {
785    /// Trading pair symbol (optional, omit for all symbols).
786    #[serde(skip_serializing_if = "Option::is_none")]
787    pub symbol: Option<String>,
788}
789
790impl TradeFeeParams {
791    /// Creates trade fee params for all symbols.
792    #[must_use]
793    pub fn all() -> Self {
794        Self { symbol: None }
795    }
796
797    /// Creates trade fee params for a specific symbol.
798    #[must_use]
799    pub fn for_symbol(symbol: impl Into<String>) -> Self {
800        Self {
801            symbol: Some(symbol.into()),
802        }
803    }
804}
805
806/// Single order in a batch order request (JSON format for batchOrders param).
807#[derive(Debug, Clone, Serialize)]
808#[serde(rename_all = "camelCase")]
809pub struct BatchOrderItem {
810    /// Trading pair symbol.
811    pub symbol: String,
812    /// Order side (BUY or SELL).
813    pub side: String,
814    /// Order type.
815    #[serde(rename = "type")]
816    pub order_type: String,
817    /// Time in force.
818    #[serde(skip_serializing_if = "Option::is_none")]
819    pub time_in_force: Option<String>,
820    /// Order quantity.
821    #[serde(skip_serializing_if = "Option::is_none")]
822    pub quantity: Option<String>,
823    /// Limit price.
824    #[serde(skip_serializing_if = "Option::is_none")]
825    pub price: Option<String>,
826    /// Client order ID.
827    #[serde(skip_serializing_if = "Option::is_none")]
828    pub new_client_order_id: Option<String>,
829    /// Stop price for stop orders.
830    #[serde(skip_serializing_if = "Option::is_none")]
831    pub stop_price: Option<String>,
832}
833
834impl BatchOrderItem {
835    /// Creates a batch order item from NewOrderParams.
836    #[must_use]
837    pub fn from_params(params: &NewOrderParams) -> Self {
838        Self {
839            symbol: params.symbol.clone(),
840            side: format!("{:?}", params.side).to_uppercase(),
841            order_type: format!("{:?}", params.order_type).to_uppercase(),
842            time_in_force: params
843                .time_in_force
844                .map(|t| format!("{t:?}").to_uppercase()),
845            quantity: params.quantity.clone(),
846            price: params.price.clone(),
847            new_client_order_id: params.new_client_order_id.clone(),
848            stop_price: params.stop_price.clone(),
849        }
850    }
851}
852
853/// Single cancel in a batch cancel request.
854#[derive(Debug, Clone, Serialize)]
855#[serde(rename_all = "camelCase")]
856pub struct BatchCancelItem {
857    /// Trading pair symbol.
858    pub symbol: String,
859    /// Order ID to cancel.
860    #[serde(skip_serializing_if = "Option::is_none")]
861    pub order_id: Option<i64>,
862    /// Original client order ID.
863    #[serde(skip_serializing_if = "Option::is_none")]
864    pub orig_client_order_id: Option<String>,
865}
866
867impl BatchCancelItem {
868    /// Creates a batch cancel item by order ID.
869    #[must_use]
870    pub fn by_order_id(symbol: impl Into<String>, order_id: i64) -> Self {
871        Self {
872            symbol: symbol.into(),
873            order_id: Some(order_id),
874            orig_client_order_id: None,
875        }
876    }
877
878    /// Creates a batch cancel item by client order ID.
879    #[must_use]
880    pub fn by_client_order_id(
881        symbol: impl Into<String>,
882        client_order_id: impl Into<String>,
883    ) -> Self {
884        Self {
885            symbol: symbol.into(),
886            order_id: None,
887            orig_client_order_id: Some(client_order_id.into()),
888        }
889    }
890}