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