Skip to main content

nautilus_binance/futures/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//! Binance Futures HTTP query parameter builders.
17
18use derive_builder::Builder;
19#[cfg(test)]
20use nautilus_core::string::secret::REDACTED;
21use nautilus_core::string::secret::SecretString;
22use serde::{Deserialize, Serialize};
23use zeroize::Zeroize;
24
25use crate::common::enums::{
26    BinanceAlgoType, BinanceFuturesOrderType, BinanceIncomeType, BinanceMarginType,
27    BinancePositionSide, BinancePriceMatch, BinanceSelfTradePreventionMode, BinanceSide,
28    BinanceTimeInForce, BinanceWorkingType,
29};
30
31/// Query parameters for `GET /fapi/v1/depth` or `GET /dapi/v1/depth`.
32#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
33#[builder(setter(into, strip_option), default)]
34pub struct BinanceDepthParams {
35    /// Trading symbol (required).
36    pub symbol: String,
37    /// Depth limit (default 100, max 1000).
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub limit: Option<u32>,
40}
41
42/// Query parameters for `GET /fapi/v1/trades` or `GET /dapi/v1/trades`.
43#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
44#[builder(setter(into, strip_option), default)]
45pub struct BinanceTradesParams {
46    /// Trading symbol (required).
47    pub symbol: String,
48    /// Number of trades to return (default 500, max 1000).
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub limit: Option<u32>,
51}
52
53/// Query parameters for `GET /fapi/v1/aggTrades` or `GET /dapi/v1/aggTrades`.
54#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
55#[builder(setter(into, strip_option), default)]
56pub struct BinanceAggTradesParams {
57    /// Trading symbol.
58    pub symbol: String,
59    /// Aggregate trade ID to begin from, inclusive.
60    #[serde(rename = "fromId", skip_serializing_if = "Option::is_none")]
61    pub from_id: Option<i64>,
62    /// Start time in milliseconds, inclusive.
63    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
64    pub start_time: Option<i64>,
65    /// End time in milliseconds, inclusive.
66    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
67    pub end_time: Option<i64>,
68    /// Number of aggregate trades to return (default 500, max 1000).
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub limit: Option<u32>,
71}
72
73/// Query parameters for `GET /fapi/v1/klines` or `GET /dapi/v1/klines`.
74#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
75#[builder(setter(into, strip_option), default)]
76pub struct BinanceKlinesParams {
77    /// Trading symbol (required).
78    pub symbol: String,
79    /// Kline interval (e.g., "1m", "5m", "1h", "1d").
80    pub interval: String,
81    /// Start time in milliseconds.
82    #[serde(skip_serializing_if = "Option::is_none")]
83    #[serde(rename = "startTime")]
84    pub start_time: Option<i64>,
85    /// End time in milliseconds.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    #[serde(rename = "endTime")]
88    pub end_time: Option<i64>,
89    /// Number of klines to return (default 500, max 1500).
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub limit: Option<u32>,
92}
93
94/// Query parameters for `GET /fapi/v1/ticker/24hr` or `GET /dapi/v1/ticker/24hr`.
95#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
96#[builder(default)]
97#[builder(setter(into, strip_option))]
98pub struct BinanceTicker24hrParams {
99    /// Filter by single symbol.
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub symbol: Option<String>,
102}
103
104/// Query parameters for `GET /fapi/v1/ticker/bookTicker` or `GET /dapi/v1/ticker/bookTicker`.
105#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
106#[builder(default)]
107#[builder(setter(into, strip_option))]
108pub struct BinanceBookTickerParams {
109    /// Filter by single symbol.
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub symbol: Option<String>,
112}
113
114/// Query parameters for `GET /fapi/v1/premiumIndex` or `GET /dapi/v1/premiumIndex`.
115#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
116#[builder(default)]
117#[builder(setter(into, strip_option))]
118pub struct BinanceMarkPriceParams {
119    /// Filter by single symbol.
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub symbol: Option<String>,
122}
123
124/// Query parameters for `GET /fapi/v1/fundingRate` or `GET /dapi/v1/fundingRate`.
125#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
126#[builder(default)]
127#[builder(setter(into, strip_option))]
128pub struct BinanceFundingRateParams {
129    /// Trading symbol.
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub symbol: Option<String>,
132    /// Start time in milliseconds.
133    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
134    pub start_time: Option<i64>,
135    /// End time in milliseconds.
136    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
137    pub end_time: Option<i64>,
138    /// Number of results (default 100, max 1000).
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub limit: Option<u32>,
141}
142
143/// Query parameters for `GET /fapi/v1/openInterest` or `GET /dapi/v1/openInterest`.
144#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
145#[builder(setter(into))]
146pub struct BinanceOpenInterestParams {
147    /// Trading symbol (required).
148    pub symbol: String,
149}
150
151/// Query parameters for `GET /futures/data/openInterestHist`.
152#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
153#[builder(setter(into, strip_option), default)]
154pub struct BinanceOpenInterestHistParams {
155    /// Trading symbol for USD-M requests.
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub symbol: Option<String>,
158    /// Trading pair for COIN-M requests.
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub pair: Option<String>,
161    /// Contract type for COIN-M requests.
162    #[serde(rename = "contractType", skip_serializing_if = "Option::is_none")]
163    pub contract_type: Option<String>,
164    /// Aggregation period (e.g. "5m", "1h").
165    pub period: String,
166    /// Start time in milliseconds.
167    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
168    pub start_time: Option<i64>,
169    /// End time in milliseconds.
170    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
171    pub end_time: Option<i64>,
172    /// Number of results to return.
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub limit: Option<u32>,
175}
176
177/// Query parameters for `GET /fapi/v2/balance` or `GET /dapi/v1/balance`.
178#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
179#[builder(default)]
180#[builder(setter(into, strip_option))]
181pub struct BinanceFuturesBalanceParams {
182    /// Filter by asset (e.g., "USDT").
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub asset: Option<String>,
185    /// Recv window override (ms).
186    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
187    pub recv_window: Option<u64>,
188}
189
190/// Query parameters for `GET /fapi/v2/positionRisk` or `GET /dapi/v1/positionRisk`.
191#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
192#[builder(default)]
193#[builder(setter(into, strip_option))]
194pub struct BinancePositionRiskParams {
195    /// Filter by symbol.
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub symbol: Option<String>,
198    /// Recv window override (ms).
199    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
200    pub recv_window: Option<u64>,
201}
202
203/// Query parameters for `GET /fapi/v1/commissionRate` or `GET /dapi/v1/commissionRate`.
204#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
205#[builder(setter(into))]
206pub struct BinanceCommissionRateParams {
207    /// Trading symbol.
208    pub symbol: String,
209}
210
211/// Query parameters for `GET /fapi/v1/income` or `GET /dapi/v1/income`.
212#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
213#[builder(default)]
214#[builder(setter(into, strip_option))]
215pub struct BinanceIncomeHistoryParams {
216    /// Filter by symbol.
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub symbol: Option<String>,
219    /// Income type filter (e.g., FUNDING_FEE).
220    #[serde(rename = "incomeType", skip_serializing_if = "Option::is_none")]
221    pub income_type: Option<BinanceIncomeType>,
222    /// Start time in milliseconds.
223    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
224    pub start_time: Option<i64>,
225    /// End time in milliseconds.
226    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
227    pub end_time: Option<i64>,
228    /// Maximum number of rows (default 100, max 1000).
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub limit: Option<u32>,
231    /// Recv window override (ms).
232    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
233    pub recv_window: Option<u64>,
234}
235
236/// Query parameters for `GET /fapi/v1/userTrades` or `GET /dapi/v1/userTrades`.
237#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
238#[builder(setter(into, strip_option), default)]
239pub struct BinanceUserTradesParams {
240    /// Trading symbol (required).
241    pub symbol: String,
242    /// Order ID to filter trades for a specific order.
243    #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
244    pub order_id: Option<i64>,
245    /// Start time in milliseconds.
246    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
247    pub start_time: Option<i64>,
248    /// End time in milliseconds.
249    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
250    pub end_time: Option<i64>,
251    /// Trade ID to fetch from (inclusive).
252    #[serde(rename = "fromId", skip_serializing_if = "Option::is_none")]
253    pub from_id: Option<i64>,
254    /// Number of trades to return (default 500, max 1000).
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub limit: Option<u32>,
257    /// Recv window override (ms).
258    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
259    pub recv_window: Option<u64>,
260}
261
262/// Query parameters for `GET /fapi/v1/openOrders` or `GET /dapi/v1/openOrders`.
263#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
264#[builder(default)]
265#[builder(setter(into, strip_option))]
266pub struct BinanceOpenOrdersParams {
267    /// Filter by symbol.
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub symbol: Option<String>,
270    /// Recv window override (ms).
271    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
272    pub recv_window: Option<u64>,
273}
274
275/// Query parameters for `GET /fapi/v1/order` or `GET /dapi/v1/order`.
276#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
277#[builder(setter(into, strip_option), default)]
278pub struct BinanceOrderQueryParams {
279    /// Trading symbol (required).
280    pub symbol: String,
281    /// Order ID.
282    #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
283    pub order_id: Option<i64>,
284    /// Orig client order ID.
285    #[serde(rename = "origClientOrderId", skip_serializing_if = "Option::is_none")]
286    pub orig_client_order_id: Option<String>,
287    /// Recv window override (ms).
288    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
289    pub recv_window: Option<u64>,
290}
291
292/// Query parameters for `POST /fapi/v1/order` (new order).
293#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
294#[builder(setter(into, strip_option))]
295pub struct BinanceNewOrderParams {
296    /// Trading symbol (required).
297    pub symbol: String,
298    /// Order side (required).
299    pub side: BinanceSide,
300    /// Order type (required).
301    #[serde(rename = "type")]
302    pub order_type: BinanceFuturesOrderType,
303    /// Position side (required for hedge mode).
304    #[serde(rename = "positionSide", skip_serializing_if = "Option::is_none")]
305    #[builder(default)]
306    pub position_side: Option<BinancePositionSide>,
307    /// Time in force.
308    #[serde(rename = "timeInForce", skip_serializing_if = "Option::is_none")]
309    #[builder(default)]
310    pub time_in_force: Option<BinanceTimeInForce>,
311    /// Order quantity.
312    #[serde(skip_serializing_if = "Option::is_none")]
313    #[builder(default)]
314    pub quantity: Option<String>,
315    /// Reduce only flag.
316    #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
317    #[builder(default)]
318    pub reduce_only: Option<bool>,
319    /// Limit price.
320    #[serde(skip_serializing_if = "Option::is_none")]
321    #[builder(default)]
322    pub price: Option<String>,
323    /// Client order ID.
324    #[serde(rename = "newClientOrderId", skip_serializing_if = "Option::is_none")]
325    #[builder(default)]
326    pub new_client_order_id: Option<String>,
327    /// Stop price.
328    #[serde(rename = "stopPrice", skip_serializing_if = "Option::is_none")]
329    #[builder(default)]
330    pub stop_price: Option<String>,
331    /// Close position flag.
332    #[serde(rename = "closePosition", skip_serializing_if = "Option::is_none")]
333    #[builder(default)]
334    pub close_position: Option<bool>,
335    /// Activation price for trailing stop.
336    #[serde(rename = "activationPrice", skip_serializing_if = "Option::is_none")]
337    #[builder(default)]
338    pub activation_price: Option<String>,
339    /// Callback rate for trailing stop.
340    #[serde(rename = "callbackRate", skip_serializing_if = "Option::is_none")]
341    #[builder(default)]
342    pub callback_rate: Option<String>,
343    /// Working type (MARK_PRICE or CONTRACT_PRICE).
344    #[serde(rename = "workingType", skip_serializing_if = "Option::is_none")]
345    #[builder(default)]
346    pub working_type: Option<BinanceWorkingType>,
347    /// Price protect flag.
348    #[serde(rename = "priceProtect", skip_serializing_if = "Option::is_none")]
349    #[builder(default)]
350    pub price_protect: Option<bool>,
351    /// Response type (ACK, RESULT, FULL).
352    #[serde(rename = "newOrderRespType", skip_serializing_if = "Option::is_none")]
353    #[builder(default)]
354    pub new_order_resp_type: Option<String>,
355    /// Good till date (for GTD orders).
356    #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
357    #[builder(default)]
358    pub good_till_date: Option<i64>,
359    /// Recv window override (ms).
360    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
361    #[builder(default)]
362    pub recv_window: Option<u64>,
363    /// Price match mode for algorithmic price matching.
364    #[serde(rename = "priceMatch", skip_serializing_if = "Option::is_none")]
365    #[builder(default)]
366    pub price_match: Option<BinancePriceMatch>,
367    /// Self-trade prevention mode.
368    #[serde(
369        rename = "selfTradePreventionMode",
370        skip_serializing_if = "Option::is_none"
371    )]
372    #[builder(default)]
373    pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
374}
375
376/// Query parameters for `DELETE /fapi/v1/order` (cancel order).
377#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
378#[builder(setter(into, strip_option), default)]
379pub struct BinanceCancelOrderParams {
380    /// Trading symbol (required).
381    pub symbol: String,
382    /// Order ID.
383    #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
384    pub order_id: Option<i64>,
385    /// Orig client order ID.
386    #[serde(rename = "origClientOrderId", skip_serializing_if = "Option::is_none")]
387    pub orig_client_order_id: Option<String>,
388    /// Recv window override (ms).
389    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
390    pub recv_window: Option<u64>,
391}
392
393/// Query parameters for `DELETE /fapi/v1/allOpenOrders` (cancel all open orders).
394#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
395#[builder(setter(into, strip_option), default)]
396pub struct BinanceCancelAllOrdersParams {
397    /// Trading symbol (required).
398    pub symbol: String,
399    /// Recv window override (ms).
400    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
401    pub recv_window: Option<u64>,
402}
403
404/// Query parameters for `PUT /fapi/v1/order` (modify order).
405#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
406#[builder(setter(into, strip_option))]
407pub struct BinanceModifyOrderParams {
408    /// Trading symbol (required).
409    pub symbol: String,
410    /// Order ID.
411    #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
412    #[builder(default)]
413    pub order_id: Option<i64>,
414    /// Orig client order ID.
415    #[serde(rename = "origClientOrderId", skip_serializing_if = "Option::is_none")]
416    #[builder(default)]
417    pub orig_client_order_id: Option<String>,
418    /// Order side (required).
419    pub side: BinanceSide,
420    /// Order quantity (required).
421    pub quantity: String,
422    /// Limit price (required).
423    pub price: String,
424    /// Recv window override (ms).
425    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
426    #[builder(default)]
427    pub recv_window: Option<u64>,
428}
429
430/// Query parameters for `GET /fapi/v1/allOrders` (all orders history).
431#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
432#[builder(setter(into, strip_option), default)]
433pub struct BinanceAllOrdersParams {
434    /// Trading symbol (required).
435    pub symbol: String,
436    /// Order ID to start from.
437    #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
438    pub order_id: Option<i64>,
439    /// Start time in milliseconds.
440    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
441    pub start_time: Option<i64>,
442    /// End time in milliseconds.
443    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
444    pub end_time: Option<i64>,
445    /// Number of results (default 500, max 1000).
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub limit: Option<u32>,
448    /// Recv window override (ms).
449    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
450    pub recv_window: Option<u64>,
451}
452
453/// Query parameters for `POST /fapi/v1/leverage` (set leverage).
454#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
455#[builder(setter(into))]
456pub struct BinanceSetLeverageParams {
457    /// Trading symbol (required).
458    pub symbol: String,
459    /// Target leverage (required).
460    pub leverage: u32,
461    /// Recv window override (ms).
462    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
463    #[builder(default)]
464    pub recv_window: Option<u64>,
465}
466
467/// Query parameters for `POST /fapi/v1/marginType` (set margin type).
468#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
469#[builder(setter(into))]
470pub struct BinanceSetMarginTypeParams {
471    /// Trading symbol (required).
472    pub symbol: String,
473    /// Margin type (required).
474    #[serde(rename = "marginType")]
475    pub margin_type: BinanceMarginType,
476    /// Recv window override (ms).
477    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
478    #[builder(default)]
479    pub recv_window: Option<u64>,
480}
481
482/// Single order item for batch submit operations.
483#[derive(Clone, Debug, Serialize)]
484#[serde(rename_all = "camelCase")]
485pub struct BatchOrderItem {
486    /// Trading symbol.
487    pub symbol: String,
488    /// Order side.
489    pub side: String,
490    /// Order type.
491    #[serde(rename = "type")]
492    pub order_type: String,
493    /// Time in force.
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub time_in_force: Option<String>,
496    /// Order quantity.
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub quantity: Option<String>,
499    /// Limit price.
500    #[serde(skip_serializing_if = "Option::is_none")]
501    pub price: Option<String>,
502    /// Reduce-only flag.
503    #[serde(skip_serializing_if = "Option::is_none")]
504    pub reduce_only: Option<bool>,
505    /// Client order ID.
506    #[serde(skip_serializing_if = "Option::is_none")]
507    pub new_client_order_id: Option<String>,
508    /// Stop price for stop orders.
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub stop_price: Option<String>,
511    /// Position side.
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub position_side: Option<String>,
514    /// Activation price for trailing stop orders.
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub activation_price: Option<String>,
517    /// Callback rate for trailing stop orders (percentage).
518    #[serde(skip_serializing_if = "Option::is_none")]
519    pub callback_rate: Option<String>,
520    /// Working type (MARK_PRICE or CONTRACT_PRICE).
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub working_type: Option<String>,
523    /// Price protection flag.
524    #[serde(skip_serializing_if = "Option::is_none")]
525    pub price_protect: Option<bool>,
526    /// Close position flag.
527    #[serde(skip_serializing_if = "Option::is_none")]
528    pub close_position: Option<bool>,
529    /// Good till date for GTD orders (ms).
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub good_till_date: Option<i64>,
532    /// Price match mode.
533    #[serde(skip_serializing_if = "Option::is_none")]
534    pub price_match: Option<String>,
535    /// Self-trade prevention mode.
536    #[serde(skip_serializing_if = "Option::is_none")]
537    pub self_trade_prevention_mode: Option<String>,
538}
539
540/// Single cancel item for batch cancel operations.
541#[derive(Clone, Debug, Serialize)]
542#[serde(rename_all = "camelCase")]
543pub struct BatchCancelItem {
544    /// Trading symbol.
545    pub symbol: String,
546    /// Order ID to cancel.
547    #[serde(skip_serializing_if = "Option::is_none")]
548    pub order_id: Option<i64>,
549    /// Original client order ID.
550    #[serde(skip_serializing_if = "Option::is_none")]
551    pub orig_client_order_id: Option<String>,
552}
553
554impl BatchCancelItem {
555    /// Creates a batch cancel item by order ID.
556    #[must_use]
557    pub fn by_order_id(symbol: impl Into<String>, order_id: i64) -> Self {
558        Self {
559            symbol: symbol.into(),
560            order_id: Some(order_id),
561            orig_client_order_id: None,
562        }
563    }
564
565    /// Creates a batch cancel item by client order ID.
566    #[must_use]
567    pub fn by_client_order_id(
568        symbol: impl Into<String>,
569        client_order_id: impl Into<String>,
570    ) -> Self {
571        Self {
572            symbol: symbol.into(),
573            order_id: None,
574            orig_client_order_id: Some(client_order_id.into()),
575        }
576    }
577}
578
579/// Single modify item for batch modify operations.
580#[derive(Clone, Debug, Serialize)]
581#[serde(rename_all = "camelCase")]
582pub struct BatchModifyItem {
583    /// Trading symbol.
584    pub symbol: String,
585    /// Order ID to modify, serialized as a string because the batch endpoint rejects JSON numbers.
586    #[serde(
587        skip_serializing_if = "Option::is_none",
588        serialize_with = "serialize_batch_order_id"
589    )]
590    pub order_id: Option<i64>,
591    /// Original client order ID.
592    #[serde(skip_serializing_if = "Option::is_none")]
593    pub orig_client_order_id: Option<String>,
594    /// New order side.
595    pub side: String,
596    /// New quantity.
597    pub quantity: String,
598    /// New price.
599    pub price: String,
600}
601
602/// Listen key request parameters.
603#[derive(Debug, Clone, Serialize, Zeroize)]
604#[serde(rename_all = "camelCase")]
605pub struct ListenKeyParams {
606    /// The listen key to extend or close.
607    pub listen_key: SecretString,
608}
609
610/// Query parameters for `POST /fapi/v1/algoOrder` (new algo order).
611///
612/// # References
613///
614/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/New-Algo-Order>
615#[derive(Clone, Debug, Serialize, Builder)]
616#[builder(setter(into, strip_option))]
617#[serde(rename_all = "camelCase")]
618pub struct BinanceNewAlgoOrderParams {
619    /// Trading symbol (required).
620    pub symbol: String,
621    /// Order side (required).
622    pub side: BinanceSide,
623    /// Order type (required): STOP_MARKET, STOP, TAKE_PROFIT, TAKE_PROFIT_MARKET, TRAILING_STOP_MARKET.
624    #[serde(rename = "type")]
625    pub order_type: BinanceFuturesOrderType,
626    /// Algo type (required). Currently only `Conditional` is supported.
627    #[serde(rename = "algoType")]
628    pub algo_type: BinanceAlgoType,
629    /// Position side (required for hedge mode).
630    #[serde(rename = "positionSide", skip_serializing_if = "Option::is_none")]
631    #[builder(default)]
632    pub position_side: Option<BinancePositionSide>,
633    /// Order quantity.
634    #[serde(skip_serializing_if = "Option::is_none")]
635    #[builder(default)]
636    pub quantity: Option<String>,
637    /// Limit price (for STOP/TAKE_PROFIT limit orders).
638    #[serde(skip_serializing_if = "Option::is_none")]
639    #[builder(default)]
640    pub price: Option<String>,
641    /// Trigger price for conditional order (required).
642    #[serde(rename = "triggerPrice", skip_serializing_if = "Option::is_none")]
643    #[builder(default)]
644    pub trigger_price: Option<String>,
645    /// Time in force.
646    #[serde(rename = "timeInForce", skip_serializing_if = "Option::is_none")]
647    #[builder(default)]
648    pub time_in_force: Option<BinanceTimeInForce>,
649    /// Working type for trigger price calculation (MARK_PRICE or CONTRACT_PRICE).
650    #[serde(rename = "workingType", skip_serializing_if = "Option::is_none")]
651    #[builder(default)]
652    pub working_type: Option<BinanceWorkingType>,
653    /// Close all position flag.
654    #[serde(rename = "closePosition", skip_serializing_if = "Option::is_none")]
655    #[builder(default)]
656    pub close_position: Option<bool>,
657    /// Price protection flag.
658    #[serde(rename = "priceProtect", skip_serializing_if = "Option::is_none")]
659    #[builder(default)]
660    pub price_protect: Option<bool>,
661    /// Reduce-only flag.
662    #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
663    #[builder(default)]
664    pub reduce_only: Option<bool>,
665    /// Activation price for TRAILING_STOP_MARKET orders.
666    #[serde(rename = "activatePrice", skip_serializing_if = "Option::is_none")]
667    #[builder(default)]
668    pub activation_price: Option<String>,
669    /// Callback rate for TRAILING_STOP_MARKET orders (0.1 to 10, where 1 = 1%).
670    #[serde(rename = "callbackRate", skip_serializing_if = "Option::is_none")]
671    #[builder(default)]
672    pub callback_rate: Option<String>,
673    /// Client algo order ID for idempotency.
674    #[serde(rename = "clientAlgoId", skip_serializing_if = "Option::is_none")]
675    #[builder(default)]
676    pub client_algo_id: Option<String>,
677    /// Good till date for GTD orders (milliseconds).
678    #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
679    #[builder(default)]
680    pub good_till_date: Option<i64>,
681    /// Recv window override (ms).
682    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
683    #[builder(default)]
684    pub recv_window: Option<u64>,
685}
686
687/// Query parameters for `GET /fapi/v1/algoOrder` and `DELETE /fapi/v1/algoOrder`.
688#[derive(Clone, Debug, Default, Serialize, Builder)]
689#[builder(setter(into, strip_option), default)]
690#[serde(rename_all = "camelCase")]
691pub struct BinanceAlgoOrderQueryParams {
692    /// Algo order ID.
693    #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
694    pub algo_id: Option<i64>,
695    /// Client algo order ID.
696    #[serde(rename = "clientAlgoId", skip_serializing_if = "Option::is_none")]
697    pub client_algo_id: Option<String>,
698    /// Recv window override (ms).
699    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
700    pub recv_window: Option<u64>,
701}
702
703/// Query parameters for `GET /fapi/v1/openAlgoOrders`.
704#[derive(Clone, Debug, Default, Serialize, Builder)]
705#[builder(setter(into, strip_option), default)]
706#[serde(rename_all = "camelCase")]
707pub struct BinanceOpenAlgoOrdersParams {
708    /// Filter by symbol (optional).
709    #[serde(skip_serializing_if = "Option::is_none")]
710    pub symbol: Option<String>,
711    /// Recv window override (ms).
712    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
713    pub recv_window: Option<u64>,
714}
715
716/// Query parameters for `GET /fapi/v1/allAlgoOrders`.
717#[derive(Clone, Debug, Serialize, Builder)]
718#[builder(setter(into, strip_option))]
719#[serde(rename_all = "camelCase")]
720pub struct BinanceAllAlgoOrdersParams {
721    /// Trading symbol (required).
722    pub symbol: String,
723    /// Return orders with an algo order ID greater than or equal to this value.
724    #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
725    #[builder(default)]
726    pub algo_id: Option<i64>,
727    /// Start time in milliseconds.
728    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
729    #[builder(default)]
730    pub start_time: Option<i64>,
731    /// End time in milliseconds.
732    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
733    #[builder(default)]
734    pub end_time: Option<i64>,
735    /// Page number (1-indexed).
736    #[serde(skip_serializing_if = "Option::is_none")]
737    #[builder(default)]
738    pub page: Option<u32>,
739    /// Number of results (default 500, max 1000).
740    #[serde(skip_serializing_if = "Option::is_none")]
741    #[builder(default)]
742    pub limit: Option<u32>,
743    /// Recv window override (ms).
744    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
745    #[builder(default)]
746    pub recv_window: Option<u64>,
747}
748
749/// Query parameters for `DELETE /fapi/v1/algoOpenOrders` (cancel all open algo orders).
750#[derive(Clone, Debug, Serialize, Builder)]
751#[builder(setter(into, strip_option))]
752#[serde(rename_all = "camelCase")]
753pub struct BinanceCancelAllAlgoOrdersParams {
754    /// Trading symbol (required).
755    pub symbol: String,
756    /// Recv window override (ms).
757    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
758    #[builder(default)]
759    pub recv_window: Option<u64>,
760}
761
762fn serialize_batch_order_id<S: serde::Serializer>(
763    order_id: &Option<i64>,
764    serializer: S,
765) -> Result<S::Ok, S::Error> {
766    match order_id {
767        Some(id) => serializer.collect_str(id),
768        None => serializer.serialize_none(),
769    }
770}
771
772#[cfg(test)]
773mod tests {
774    use rstest::rstest;
775    use zeroize::Zeroize;
776
777    use super::*;
778
779    #[rstest]
780    fn test_listen_key_params_preserve_wire_value_and_redact_debug() {
781        let mut params = ListenKeyParams {
782            listen_key: SecretString::from("listen-key-secret"),
783        };
784
785        let serialized = serde_urlencoded::to_string(&params).unwrap();
786        let debug = format!("{params:?}");
787
788        assert_eq!(serialized, "listenKey=listen-key-secret");
789        assert!(debug.contains(REDACTED));
790        assert!(!debug.contains(params.listen_key.expose_secret()));
791
792        params.zeroize();
793        assert!(params.listen_key.expose_secret().is_empty());
794    }
795
796    #[rstest]
797    fn test_depth_params_builder() {
798        let params = BinanceDepthParamsBuilder::default()
799            .symbol("BTCUSDT")
800            .limit(100u32)
801            .build()
802            .unwrap();
803
804        assert_eq!(params.symbol, "BTCUSDT");
805        assert_eq!(params.limit, Some(100));
806    }
807
808    #[rstest]
809    fn test_ticker_params_serialization() {
810        let params = BinanceTicker24hrParams {
811            symbol: Some("BTCUSDT".to_string()),
812        };
813
814        let serialized = serde_urlencoded::to_string(&params).unwrap();
815        assert_eq!(serialized, "symbol=BTCUSDT");
816    }
817
818    #[rstest]
819    fn test_agg_trades_params_serialization() {
820        let params = BinanceAggTradesParams {
821            symbol: "BTCUSDT".to_string(),
822            from_id: Some(123),
823            start_time: Some(1_700_000_000_001),
824            end_time: Some(1_700_000_000_999),
825            limit: Some(456),
826        };
827
828        let serialized = serde_urlencoded::to_string(&params).unwrap();
829
830        assert_eq!(
831            serialized,
832            "symbol=BTCUSDT&fromId=123&startTime=1700000000001&endTime=1700000000999&limit=456"
833        );
834    }
835
836    #[rstest]
837    fn test_order_query_params_builder() {
838        let params = BinanceOrderQueryParamsBuilder::default()
839            .symbol("BTCUSDT")
840            .order_id(12345_i64)
841            .recv_window(5_000_u64)
842            .build()
843            .unwrap();
844
845        assert_eq!(params.symbol, "BTCUSDT");
846        assert_eq!(params.order_id, Some(12345));
847        assert_eq!(params.recv_window, Some(5_000));
848    }
849
850    #[rstest]
851    fn test_income_history_params_serialization() {
852        let params = BinanceIncomeHistoryParamsBuilder::default()
853            .symbol("ETHUSDT")
854            .income_type(BinanceIncomeType::FundingFee)
855            .limit(50_u32)
856            .build()
857            .unwrap();
858
859        let serialized = serde_urlencoded::to_string(&params).unwrap();
860        assert_eq!(serialized, "symbol=ETHUSDT&incomeType=FUNDING_FEE&limit=50");
861    }
862
863    #[rstest]
864    fn test_open_orders_params_builder() {
865        let params = BinanceOpenOrdersParamsBuilder::default()
866            .symbol("BNBUSDT")
867            .build()
868            .unwrap();
869
870        assert_eq!(params.symbol.as_deref(), Some("BNBUSDT"));
871        assert!(params.recv_window.is_none());
872    }
873
874    #[rstest]
875    fn test_new_algo_order_params_serialization_uses_activate_price() {
876        let params = BinanceNewAlgoOrderParamsBuilder::default()
877            .symbol("ETHUSDT")
878            .side(BinanceSide::Sell)
879            .order_type(BinanceFuturesOrderType::TrailingStopMarket)
880            .algo_type(BinanceAlgoType::Conditional)
881            .quantity("0.1")
882            .activation_price("10000.00")
883            .callback_rate("0.25")
884            .build()
885            .unwrap();
886
887        let serialized = serde_urlencoded::to_string(&params).unwrap();
888        let query: std::collections::HashMap<String, String> =
889            serde_urlencoded::from_str(&serialized).unwrap();
890
891        assert_eq!(query.get("activatePrice"), Some(&"10000.00".to_string()));
892        assert_eq!(query.get("callbackRate"), Some(&"0.25".to_string()));
893        assert!(!query.contains_key("activationPrice"));
894    }
895
896    #[rstest]
897    fn test_new_order_params_with_price_match_serializes_correctly() {
898        let params = BinanceNewOrderParams {
899            symbol: "BTCUSDT".to_string(),
900            side: BinanceSide::Buy,
901            order_type: BinanceFuturesOrderType::Limit,
902            time_in_force: Some(BinanceTimeInForce::Gtc),
903            quantity: Some("0.001".to_string()),
904            price: None,
905            new_client_order_id: Some("test-order-001".to_string()),
906            stop_price: None,
907            reduce_only: None,
908            position_side: None,
909            close_position: None,
910            activation_price: None,
911            callback_rate: None,
912            working_type: None,
913            price_protect: None,
914            new_order_resp_type: None,
915            good_till_date: None,
916            recv_window: None,
917            price_match: Some(BinancePriceMatch::Opponent5),
918            self_trade_prevention_mode: None,
919        };
920
921        let serialized = serde_urlencoded::to_string(&params).unwrap();
922        let query: std::collections::HashMap<String, String> =
923            serde_urlencoded::from_str(&serialized).unwrap();
924
925        assert_eq!(query.get("priceMatch"), Some(&"OPPONENT_5".to_string()));
926        assert!(!query.contains_key("price"));
927        assert_eq!(query.get("symbol"), Some(&"BTCUSDT".to_string()));
928        assert_eq!(query.get("side"), Some(&"BUY".to_string()));
929        assert_eq!(query.get("type"), Some(&"LIMIT".to_string()));
930    }
931
932    #[rstest]
933    fn test_new_order_params_without_price_match_omits_field() {
934        let params = BinanceNewOrderParams {
935            symbol: "BTCUSDT".to_string(),
936            side: BinanceSide::Buy,
937            order_type: BinanceFuturesOrderType::Limit,
938            time_in_force: Some(BinanceTimeInForce::Gtc),
939            quantity: Some("0.001".to_string()),
940            price: Some("50000.00".to_string()),
941            new_client_order_id: Some("test-order-002".to_string()),
942            stop_price: None,
943            reduce_only: None,
944            position_side: None,
945            close_position: None,
946            activation_price: None,
947            callback_rate: None,
948            working_type: None,
949            price_protect: None,
950            new_order_resp_type: None,
951            good_till_date: None,
952            recv_window: None,
953            price_match: None,
954            self_trade_prevention_mode: None,
955        };
956
957        let serialized = serde_urlencoded::to_string(&params).unwrap();
958        let query: std::collections::HashMap<String, String> =
959            serde_urlencoded::from_str(&serialized).unwrap();
960
961        assert!(!query.contains_key("priceMatch"));
962        assert_eq!(query.get("price"), Some(&"50000.00".to_string()));
963    }
964}