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