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