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.
586    #[serde(skip_serializing_if = "Option::is_none")]
587    pub order_id: Option<i64>,
588    /// Original client order ID.
589    #[serde(skip_serializing_if = "Option::is_none")]
590    pub orig_client_order_id: Option<String>,
591    /// New order side.
592    pub side: String,
593    /// New quantity.
594    pub quantity: String,
595    /// New price.
596    pub price: String,
597}
598
599/// Listen key request parameters.
600#[derive(Debug, Clone, Serialize, Zeroize)]
601#[serde(rename_all = "camelCase")]
602pub struct ListenKeyParams {
603    /// The listen key to extend or close.
604    pub listen_key: SecretString,
605}
606
607/// Query parameters for `POST /fapi/v1/algoOrder` (new algo order).
608///
609/// # References
610///
611/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/New-Algo-Order>
612#[derive(Clone, Debug, Serialize, Builder)]
613#[builder(setter(into, strip_option))]
614#[serde(rename_all = "camelCase")]
615pub struct BinanceNewAlgoOrderParams {
616    /// Trading symbol (required).
617    pub symbol: String,
618    /// Order side (required).
619    pub side: BinanceSide,
620    /// Order type (required): STOP_MARKET, STOP, TAKE_PROFIT, TAKE_PROFIT_MARKET, TRAILING_STOP_MARKET.
621    #[serde(rename = "type")]
622    pub order_type: BinanceFuturesOrderType,
623    /// Algo type (required). Currently only `Conditional` is supported.
624    #[serde(rename = "algoType")]
625    pub algo_type: BinanceAlgoType,
626    /// Position side (required for hedge mode).
627    #[serde(rename = "positionSide", skip_serializing_if = "Option::is_none")]
628    #[builder(default)]
629    pub position_side: Option<BinancePositionSide>,
630    /// Order quantity.
631    #[serde(skip_serializing_if = "Option::is_none")]
632    #[builder(default)]
633    pub quantity: Option<String>,
634    /// Limit price (for STOP/TAKE_PROFIT limit orders).
635    #[serde(skip_serializing_if = "Option::is_none")]
636    #[builder(default)]
637    pub price: Option<String>,
638    /// Trigger price for conditional order (required).
639    #[serde(rename = "triggerPrice", skip_serializing_if = "Option::is_none")]
640    #[builder(default)]
641    pub trigger_price: Option<String>,
642    /// Time in force.
643    #[serde(rename = "timeInForce", skip_serializing_if = "Option::is_none")]
644    #[builder(default)]
645    pub time_in_force: Option<BinanceTimeInForce>,
646    /// Working type for trigger price calculation (MARK_PRICE or CONTRACT_PRICE).
647    #[serde(rename = "workingType", skip_serializing_if = "Option::is_none")]
648    #[builder(default)]
649    pub working_type: Option<BinanceWorkingType>,
650    /// Close all position flag.
651    #[serde(rename = "closePosition", skip_serializing_if = "Option::is_none")]
652    #[builder(default)]
653    pub close_position: Option<bool>,
654    /// Price protection flag.
655    #[serde(rename = "priceProtect", skip_serializing_if = "Option::is_none")]
656    #[builder(default)]
657    pub price_protect: Option<bool>,
658    /// Reduce-only flag.
659    #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
660    #[builder(default)]
661    pub reduce_only: Option<bool>,
662    /// Activation price for TRAILING_STOP_MARKET orders.
663    #[serde(rename = "activatePrice", skip_serializing_if = "Option::is_none")]
664    #[builder(default)]
665    pub activation_price: Option<String>,
666    /// Callback rate for TRAILING_STOP_MARKET orders (0.1 to 10, where 1 = 1%).
667    #[serde(rename = "callbackRate", skip_serializing_if = "Option::is_none")]
668    #[builder(default)]
669    pub callback_rate: Option<String>,
670    /// Client algo order ID for idempotency.
671    #[serde(rename = "clientAlgoId", skip_serializing_if = "Option::is_none")]
672    #[builder(default)]
673    pub client_algo_id: Option<String>,
674    /// Good till date for GTD orders (milliseconds).
675    #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
676    #[builder(default)]
677    pub good_till_date: Option<i64>,
678    /// Recv window override (ms).
679    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
680    #[builder(default)]
681    pub recv_window: Option<u64>,
682}
683
684/// Query parameters for `GET /fapi/v1/algoOrder` and `DELETE /fapi/v1/algoOrder`.
685#[derive(Clone, Debug, Default, Serialize, Builder)]
686#[builder(setter(into, strip_option), default)]
687#[serde(rename_all = "camelCase")]
688pub struct BinanceAlgoOrderQueryParams {
689    /// Algo order ID.
690    #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
691    pub algo_id: Option<i64>,
692    /// Client algo order ID.
693    #[serde(rename = "clientAlgoId", skip_serializing_if = "Option::is_none")]
694    pub client_algo_id: Option<String>,
695    /// Recv window override (ms).
696    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
697    pub recv_window: Option<u64>,
698}
699
700/// Query parameters for `GET /fapi/v1/openAlgoOrders`.
701#[derive(Clone, Debug, Default, Serialize, Builder)]
702#[builder(setter(into, strip_option), default)]
703#[serde(rename_all = "camelCase")]
704pub struct BinanceOpenAlgoOrdersParams {
705    /// Filter by symbol (optional).
706    #[serde(skip_serializing_if = "Option::is_none")]
707    pub symbol: Option<String>,
708    /// Recv window override (ms).
709    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
710    pub recv_window: Option<u64>,
711}
712
713/// Query parameters for `GET /fapi/v1/allAlgoOrders`.
714#[derive(Clone, Debug, Serialize, Builder)]
715#[builder(setter(into, strip_option))]
716#[serde(rename_all = "camelCase")]
717pub struct BinanceAllAlgoOrdersParams {
718    /// Trading symbol (required).
719    pub symbol: String,
720    /// Return orders with an algo order ID greater than or equal to this value.
721    #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
722    #[builder(default)]
723    pub algo_id: Option<i64>,
724    /// Start time in milliseconds.
725    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
726    #[builder(default)]
727    pub start_time: Option<i64>,
728    /// End time in milliseconds.
729    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
730    #[builder(default)]
731    pub end_time: Option<i64>,
732    /// Page number (1-indexed).
733    #[serde(skip_serializing_if = "Option::is_none")]
734    #[builder(default)]
735    pub page: Option<u32>,
736    /// Number of results (default 500, max 1000).
737    #[serde(skip_serializing_if = "Option::is_none")]
738    #[builder(default)]
739    pub limit: Option<u32>,
740    /// Recv window override (ms).
741    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
742    #[builder(default)]
743    pub recv_window: Option<u64>,
744}
745
746/// Query parameters for `DELETE /fapi/v1/algoOpenOrders` (cancel all open algo orders).
747#[derive(Clone, Debug, Serialize, Builder)]
748#[builder(setter(into, strip_option))]
749#[serde(rename_all = "camelCase")]
750pub struct BinanceCancelAllAlgoOrdersParams {
751    /// Trading symbol (required).
752    pub symbol: String,
753    /// Recv window override (ms).
754    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
755    #[builder(default)]
756    pub recv_window: Option<u64>,
757}
758
759#[cfg(test)]
760mod tests {
761    use rstest::rstest;
762    use zeroize::Zeroize;
763
764    use super::*;
765
766    #[rstest]
767    fn test_listen_key_params_preserve_wire_value_and_redact_debug() {
768        let mut params = ListenKeyParams {
769            listen_key: SecretString::from("listen-key-secret"),
770        };
771
772        let serialized = serde_urlencoded::to_string(&params).unwrap();
773        let debug = format!("{params:?}");
774
775        assert_eq!(serialized, "listenKey=listen-key-secret");
776        assert!(debug.contains(REDACTED));
777        assert!(!debug.contains(params.listen_key.expose_secret()));
778
779        params.zeroize();
780        assert!(params.listen_key.expose_secret().is_empty());
781    }
782
783    #[rstest]
784    fn test_depth_params_builder() {
785        let params = BinanceDepthParamsBuilder::default()
786            .symbol("BTCUSDT")
787            .limit(100u32)
788            .build()
789            .unwrap();
790
791        assert_eq!(params.symbol, "BTCUSDT");
792        assert_eq!(params.limit, Some(100));
793    }
794
795    #[rstest]
796    fn test_ticker_params_serialization() {
797        let params = BinanceTicker24hrParams {
798            symbol: Some("BTCUSDT".to_string()),
799        };
800
801        let serialized = serde_urlencoded::to_string(&params).unwrap();
802        assert_eq!(serialized, "symbol=BTCUSDT");
803    }
804
805    #[rstest]
806    fn test_agg_trades_params_serialization() {
807        let params = BinanceAggTradesParams {
808            symbol: "BTCUSDT".to_string(),
809            from_id: Some(123),
810            start_time: Some(1_700_000_000_001),
811            end_time: Some(1_700_000_000_999),
812            limit: Some(456),
813        };
814
815        let serialized = serde_urlencoded::to_string(&params).unwrap();
816
817        assert_eq!(
818            serialized,
819            "symbol=BTCUSDT&fromId=123&startTime=1700000000001&endTime=1700000000999&limit=456"
820        );
821    }
822
823    #[rstest]
824    fn test_order_query_params_builder() {
825        let params = BinanceOrderQueryParamsBuilder::default()
826            .symbol("BTCUSDT")
827            .order_id(12345_i64)
828            .recv_window(5_000_u64)
829            .build()
830            .unwrap();
831
832        assert_eq!(params.symbol, "BTCUSDT");
833        assert_eq!(params.order_id, Some(12345));
834        assert_eq!(params.recv_window, Some(5_000));
835    }
836
837    #[rstest]
838    fn test_income_history_params_serialization() {
839        let params = BinanceIncomeHistoryParamsBuilder::default()
840            .symbol("ETHUSDT")
841            .income_type(BinanceIncomeType::FundingFee)
842            .limit(50_u32)
843            .build()
844            .unwrap();
845
846        let serialized = serde_urlencoded::to_string(&params).unwrap();
847        assert_eq!(serialized, "symbol=ETHUSDT&incomeType=FUNDING_FEE&limit=50");
848    }
849
850    #[rstest]
851    fn test_open_orders_params_builder() {
852        let params = BinanceOpenOrdersParamsBuilder::default()
853            .symbol("BNBUSDT")
854            .build()
855            .unwrap();
856
857        assert_eq!(params.symbol.as_deref(), Some("BNBUSDT"));
858        assert!(params.recv_window.is_none());
859    }
860
861    #[rstest]
862    fn test_new_algo_order_params_serialization_uses_activate_price() {
863        let params = BinanceNewAlgoOrderParamsBuilder::default()
864            .symbol("ETHUSDT")
865            .side(BinanceSide::Sell)
866            .order_type(BinanceFuturesOrderType::TrailingStopMarket)
867            .algo_type(BinanceAlgoType::Conditional)
868            .quantity("0.1")
869            .activation_price("10000.00")
870            .callback_rate("0.25")
871            .build()
872            .unwrap();
873
874        let serialized = serde_urlencoded::to_string(&params).unwrap();
875        let query: std::collections::HashMap<String, String> =
876            serde_urlencoded::from_str(&serialized).unwrap();
877
878        assert_eq!(query.get("activatePrice"), Some(&"10000.00".to_string()));
879        assert_eq!(query.get("callbackRate"), Some(&"0.25".to_string()));
880        assert!(!query.contains_key("activationPrice"));
881    }
882
883    #[rstest]
884    fn test_new_order_params_with_price_match_serializes_correctly() {
885        let params = BinanceNewOrderParams {
886            symbol: "BTCUSDT".to_string(),
887            side: BinanceSide::Buy,
888            order_type: BinanceFuturesOrderType::Limit,
889            time_in_force: Some(BinanceTimeInForce::Gtc),
890            quantity: Some("0.001".to_string()),
891            price: None,
892            new_client_order_id: Some("test-order-001".to_string()),
893            stop_price: None,
894            reduce_only: None,
895            position_side: None,
896            close_position: None,
897            activation_price: None,
898            callback_rate: None,
899            working_type: None,
900            price_protect: None,
901            new_order_resp_type: None,
902            good_till_date: None,
903            recv_window: None,
904            price_match: Some(BinancePriceMatch::Opponent5),
905            self_trade_prevention_mode: None,
906        };
907
908        let serialized = serde_urlencoded::to_string(&params).unwrap();
909        let query: std::collections::HashMap<String, String> =
910            serde_urlencoded::from_str(&serialized).unwrap();
911
912        assert_eq!(query.get("priceMatch"), Some(&"OPPONENT_5".to_string()));
913        assert!(!query.contains_key("price"));
914        assert_eq!(query.get("symbol"), Some(&"BTCUSDT".to_string()));
915        assert_eq!(query.get("side"), Some(&"BUY".to_string()));
916        assert_eq!(query.get("type"), Some(&"LIMIT".to_string()));
917    }
918
919    #[rstest]
920    fn test_new_order_params_without_price_match_omits_field() {
921        let params = BinanceNewOrderParams {
922            symbol: "BTCUSDT".to_string(),
923            side: BinanceSide::Buy,
924            order_type: BinanceFuturesOrderType::Limit,
925            time_in_force: Some(BinanceTimeInForce::Gtc),
926            quantity: Some("0.001".to_string()),
927            price: Some("50000.00".to_string()),
928            new_client_order_id: Some("test-order-002".to_string()),
929            stop_price: None,
930            reduce_only: None,
931            position_side: None,
932            close_position: None,
933            activation_price: None,
934            callback_rate: None,
935            working_type: None,
936            price_protect: None,
937            new_order_resp_type: None,
938            good_till_date: None,
939            recv_window: None,
940            price_match: None,
941            self_trade_prevention_mode: None,
942        };
943
944        let serialized = serde_urlencoded::to_string(&params).unwrap();
945        let query: std::collections::HashMap<String, String> =
946            serde_urlencoded::from_str(&serialized).unwrap();
947
948        assert!(!query.contains_key("priceMatch"));
949        assert_eq!(query.get("price"), Some(&"50000.00".to_string()));
950    }
951}