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