nautilus_binance/http/
query.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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 HTTP query parameter builders.
17//!
18//! This module provides builder types for constructing query parameters
19//! for Binance REST API endpoints.
20
21use derive_builder::Builder;
22use serde::{Deserialize, Serialize};
23
24use crate::common::enums::BinanceIncomeType;
25
26/// Query parameters for `GET /api/v3/exchangeInfo` (Spot).
27///
28/// # References
29/// - <https://developers.binance.com/docs/binance-spot-api-docs/rest-api/general-endpoints>
30#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
31#[builder(default)]
32#[builder(setter(into, strip_option))]
33pub struct BinanceSpotExchangeInfoParams {
34    /// Filter by single symbol.
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub symbol: Option<String>,
37    /// Filter by multiple symbols (JSON array format).
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub symbols: Option<String>,
40    /// Filter by permissions.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub permissions: Option<String>,
43}
44
45/// Kline interval enumeration.
46///
47/// # References
48/// - <https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints>
49#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
50pub enum BinanceKlineInterval {
51    /// 1 second (only for spot).
52    #[serde(rename = "1s")]
53    Second1,
54    /// 1 minute.
55    #[default]
56    #[serde(rename = "1m")]
57    Minute1,
58    /// 3 minutes.
59    #[serde(rename = "3m")]
60    Minute3,
61    /// 5 minutes.
62    #[serde(rename = "5m")]
63    Minute5,
64    /// 15 minutes.
65    #[serde(rename = "15m")]
66    Minute15,
67    /// 30 minutes.
68    #[serde(rename = "30m")]
69    Minute30,
70    /// 1 hour.
71    #[serde(rename = "1h")]
72    Hour1,
73    /// 2 hours.
74    #[serde(rename = "2h")]
75    Hour2,
76    /// 4 hours.
77    #[serde(rename = "4h")]
78    Hour4,
79    /// 6 hours.
80    #[serde(rename = "6h")]
81    Hour6,
82    /// 8 hours.
83    #[serde(rename = "8h")]
84    Hour8,
85    /// 12 hours.
86    #[serde(rename = "12h")]
87    Hour12,
88    /// 1 day.
89    #[serde(rename = "1d")]
90    Day1,
91    /// 3 days.
92    #[serde(rename = "3d")]
93    Day3,
94    /// 1 week.
95    #[serde(rename = "1w")]
96    Week1,
97    /// 1 month.
98    #[serde(rename = "1M")]
99    Month1,
100}
101
102impl BinanceKlineInterval {
103    /// Returns the string representation for API requests.
104    #[must_use]
105    pub const fn as_str(self) -> &'static str {
106        match self {
107            Self::Second1 => "1s",
108            Self::Minute1 => "1m",
109            Self::Minute3 => "3m",
110            Self::Minute5 => "5m",
111            Self::Minute15 => "15m",
112            Self::Minute30 => "30m",
113            Self::Hour1 => "1h",
114            Self::Hour2 => "2h",
115            Self::Hour4 => "4h",
116            Self::Hour6 => "6h",
117            Self::Hour8 => "8h",
118            Self::Hour12 => "12h",
119            Self::Day1 => "1d",
120            Self::Day3 => "3d",
121            Self::Week1 => "1w",
122            Self::Month1 => "1M",
123        }
124    }
125
126    /// Returns the interval duration in milliseconds.
127    #[must_use]
128    pub const fn as_millis(self) -> i64 {
129        match self {
130            Self::Second1 => 1_000,
131            Self::Minute1 => 60_000,
132            Self::Minute3 => 180_000,
133            Self::Minute5 => 300_000,
134            Self::Minute15 => 900_000,
135            Self::Minute30 => 1_800_000,
136            Self::Hour1 => 3_600_000,
137            Self::Hour2 => 7_200_000,
138            Self::Hour4 => 14_400_000,
139            Self::Hour6 => 21_600_000,
140            Self::Hour8 => 28_800_000,
141            Self::Hour12 => 43_200_000,
142            Self::Day1 => 86_400_000,
143            Self::Day3 => 259_200_000,
144            Self::Week1 => 604_800_000,
145            Self::Month1 => 2_592_000_000, // Approximate 30 days
146        }
147    }
148}
149
150impl std::fmt::Display for BinanceKlineInterval {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        write!(f, "{}", self.as_str())
153    }
154}
155
156/// Query parameters for `GET /api/v3/klines` (Spot) or `GET /fapi/v1/klines` (Futures).
157///
158/// # References
159/// - <https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints>
160#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
161#[builder(setter(into, strip_option), default)]
162pub struct BinanceKlinesParams {
163    /// Trading symbol (required).
164    pub symbol: String,
165    /// Kline interval (required).
166    pub interval: BinanceKlineInterval,
167    /// Start time in milliseconds.
168    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
169    pub start_time: Option<i64>,
170    /// End time in milliseconds.
171    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
172    pub end_time: Option<i64>,
173    /// Timezone offset (spot only).
174    #[serde(rename = "timeZone", skip_serializing_if = "Option::is_none")]
175    pub time_zone: Option<String>,
176    /// Number of results (default 500, max 1000 for spot, 1500 for futures).
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub limit: Option<u32>,
179}
180
181impl Default for BinanceKlinesParams {
182    fn default() -> Self {
183        Self {
184            symbol: String::new(),
185            interval: BinanceKlineInterval::Minute1,
186            start_time: None,
187            end_time: None,
188            time_zone: None,
189            limit: None,
190        }
191    }
192}
193
194/// Query parameters for `GET /api/v3/trades` (Spot) or `GET /fapi/v1/trades` (Futures).
195///
196/// # References
197/// - <https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints>
198#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
199#[builder(setter(into, strip_option), default)]
200pub struct BinanceTradesParams {
201    /// Trading symbol (required).
202    pub symbol: String,
203    /// Number of trades to return (default 500, max 1000).
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub limit: Option<u32>,
206}
207
208/// Query parameters for `GET /api/v3/aggTrades` (Spot) or `GET /fapi/v1/aggTrades` (Futures).
209///
210/// # References
211/// - <https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints>
212#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
213#[builder(setter(into, strip_option), default)]
214pub struct BinanceAggTradesParams {
215    /// Trading symbol (required).
216    pub symbol: String,
217    /// Trade ID to fetch from (inclusive).
218    #[serde(rename = "fromId", skip_serializing_if = "Option::is_none")]
219    pub from_id: Option<i64>,
220    /// Start time in milliseconds.
221    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
222    pub start_time: Option<i64>,
223    /// End time in milliseconds.
224    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
225    pub end_time: Option<i64>,
226    /// Number of trades to return (default 500, max 1000).
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub limit: Option<u32>,
229}
230
231/// Query parameters for `GET /api/v3/depth` (Spot) or `GET /fapi/v1/depth` (Futures).
232///
233/// # References
234/// - <https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints>
235#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
236#[builder(setter(into, strip_option), default)]
237pub struct BinanceDepthParams {
238    /// Trading symbol (required).
239    pub symbol: String,
240    /// Depth limit (default 100, max 5000 for spot, max 1000 for futures).
241    /// Valid values for spot: 5, 10, 20, 50, 100, 500, 1000, 5000.
242    /// Valid values for futures: 5, 10, 20, 50, 100, 500, 1000.
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub limit: Option<u32>,
245}
246
247/// Query parameters for `GET /api/v3/ticker/24hr` (Spot) or `GET /fapi/v1/ticker/24hr` (Futures).
248///
249/// # References
250/// - <https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints>
251#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
252#[builder(default)]
253#[builder(setter(into, strip_option))]
254pub struct BinanceTicker24hrParams {
255    /// Filter by single symbol.
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub symbol: Option<String>,
258    /// Filter by multiple symbols (JSON array format, spot only).
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub symbols: Option<String>,
261    /// Response type: FULL or MINI (spot only).
262    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
263    pub response_type: Option<String>,
264}
265
266/// Query parameters for `GET /api/v3/ticker/price` or `GET /fapi/v1/ticker/price`.
267///
268/// # References
269/// - <https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints>
270#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
271#[builder(default)]
272#[builder(setter(into, strip_option))]
273pub struct BinancePriceTickerParams {
274    /// Filter by single symbol.
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub symbol: Option<String>,
277    /// Filter by multiple symbols (JSON array format, spot only).
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub symbols: Option<String>,
280}
281
282/// Query parameters for `GET /api/v3/ticker/bookTicker` or `GET /fapi/v1/ticker/bookTicker`.
283///
284/// # References
285/// - <https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints>
286#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
287#[builder(default)]
288#[builder(setter(into, strip_option))]
289pub struct BinanceBookTickerParams {
290    /// Filter by single symbol.
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub symbol: Option<String>,
293    /// Filter by multiple symbols (JSON array format, spot only).
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub symbols: Option<String>,
296}
297
298/// Query parameters for `GET /fapi/v1/premiumIndex` or `GET /dapi/v1/premiumIndex`.
299///
300/// # References
301/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Mark-Price>
302#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
303#[builder(default)]
304#[builder(setter(into, strip_option))]
305pub struct BinanceMarkPriceParams {
306    /// Filter by single symbol.
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub symbol: Option<String>,
309}
310
311/// Query parameters for `GET /fapi/v1/fundingRate` or `GET /dapi/v1/fundingRate`.
312///
313/// # References
314/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Get-Funding-Rate-History>
315#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
316#[builder(default)]
317#[builder(setter(into, strip_option))]
318pub struct BinanceFundingRateParams {
319    /// Trading symbol.
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub symbol: Option<String>,
322    /// Start time in milliseconds.
323    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
324    pub start_time: Option<i64>,
325    /// End time in milliseconds.
326    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
327    pub end_time: Option<i64>,
328    /// Number of results (default 100, max 1000).
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub limit: Option<u32>,
331}
332
333/// Query parameters for `GET /fapi/v1/openInterest` or `GET /dapi/v1/openInterest`.
334///
335/// # References
336/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Open-Interest>
337#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
338#[builder(setter(into))]
339pub struct BinanceOpenInterestParams {
340    /// Trading symbol (required).
341    pub symbol: String,
342}
343
344/// Query parameters for `GET /fapi/v2/balance` or `GET /dapi/v1/balance`.
345///
346/// # References
347/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data/account>
348#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
349#[builder(default)]
350#[builder(setter(into, strip_option))]
351pub struct BinanceFuturesBalanceParams {
352    /// Filter by asset (e.g., "USDT").
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub asset: Option<String>,
355    /// Recv window override (ms).
356    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
357    pub recv_window: Option<u64>,
358}
359
360/// Query parameters for `GET /fapi/v2/positionRisk` or `GET /dapi/v1/positionRisk`.
361///
362/// # References
363/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data/account#position-information-v2-user_data>
364#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
365#[builder(default)]
366#[builder(setter(into, strip_option))]
367pub struct BinancePositionRiskParams {
368    /// Filter by symbol.
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub symbol: Option<String>,
371    /// Recv window override (ms).
372    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
373    pub recv_window: Option<u64>,
374}
375
376/// Query parameters for `GET /fapi/v1/income` or `GET /dapi/v1/income`.
377///
378/// # References
379/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data/account#income-history-user_data>
380#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
381#[builder(default)]
382#[builder(setter(into, strip_option))]
383pub struct BinanceIncomeHistoryParams {
384    /// Filter by symbol.
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub symbol: Option<String>,
387    /// Income type filter (e.g., FUNDING_FEE).
388    #[serde(rename = "incomeType", skip_serializing_if = "Option::is_none")]
389    pub income_type: Option<BinanceIncomeType>,
390    /// Start time in milliseconds.
391    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
392    pub start_time: Option<i64>,
393    /// End time in milliseconds.
394    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
395    pub end_time: Option<i64>,
396    /// Maximum number of rows (default 100, max 1000).
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub limit: Option<u32>,
399    /// Recv window override (ms).
400    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
401    pub recv_window: Option<u64>,
402}
403
404/// Query parameters for `GET /fapi/v1/userTrades` or `GET /dapi/v1/userTrades`.
405///
406/// # References
407/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data/trade#account-trade-list-user_data>
408#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
409#[builder(setter(into, strip_option), default)]
410pub struct BinanceUserTradesParams {
411    /// Trading symbol (required).
412    pub symbol: String,
413    /// Start time in milliseconds.
414    #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
415    pub start_time: Option<i64>,
416    /// End time in milliseconds.
417    #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
418    pub end_time: Option<i64>,
419    /// Trade ID to fetch from (inclusive).
420    #[serde(rename = "fromId", skip_serializing_if = "Option::is_none")]
421    pub from_id: Option<i64>,
422    /// Number of trades to return (default 500, max 1000).
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub limit: Option<u32>,
425    /// Recv window override (ms).
426    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
427    pub recv_window: Option<u64>,
428}
429
430/// Query parameters for `GET /fapi/v1/openOrders` or `GET /dapi/v1/openOrders`.
431///
432/// # References
433/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data/order#current-all-open-orders-user_data>
434#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
435#[builder(default)]
436#[builder(setter(into, strip_option))]
437pub struct BinanceOpenOrdersParams {
438    /// Filter by symbol.
439    #[serde(skip_serializing_if = "Option::is_none")]
440    pub symbol: Option<String>,
441    /// Recv window override (ms).
442    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
443    pub recv_window: Option<u64>,
444}
445
446/// Query parameters for `GET /fapi/v1/order` or `GET /dapi/v1/order`.
447///
448/// # References
449/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data/order#query-order-user_data>
450#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
451#[builder(setter(into, strip_option), default)]
452pub struct BinanceOrderQueryParams {
453    /// Trading symbol (required).
454    pub symbol: String,
455    /// Order ID.
456    #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
457    pub order_id: Option<i64>,
458    /// Orig client order ID.
459    #[serde(rename = "origClientOrderId", skip_serializing_if = "Option::is_none")]
460    pub orig_client_order_id: Option<String>,
461    /// Recv window override (ms).
462    #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
463    pub recv_window: Option<u64>,
464}
465
466#[cfg(test)]
467mod tests {
468    use rstest::rstest;
469
470    use super::*;
471
472    #[rstest]
473    fn test_kline_interval_as_str() {
474        assert_eq!(BinanceKlineInterval::Second1.as_str(), "1s");
475        assert_eq!(BinanceKlineInterval::Minute1.as_str(), "1m");
476        assert_eq!(BinanceKlineInterval::Hour1.as_str(), "1h");
477        assert_eq!(BinanceKlineInterval::Day1.as_str(), "1d");
478        assert_eq!(BinanceKlineInterval::Week1.as_str(), "1w");
479        assert_eq!(BinanceKlineInterval::Month1.as_str(), "1M");
480    }
481
482    #[rstest]
483    fn test_kline_interval_as_millis() {
484        assert_eq!(BinanceKlineInterval::Second1.as_millis(), 1_000);
485        assert_eq!(BinanceKlineInterval::Minute1.as_millis(), 60_000);
486        assert_eq!(BinanceKlineInterval::Hour1.as_millis(), 3_600_000);
487        assert_eq!(BinanceKlineInterval::Day1.as_millis(), 86_400_000);
488    }
489
490    #[rstest]
491    fn test_klines_params_builder() {
492        let params = BinanceKlinesParamsBuilder::default()
493            .symbol("BTCUSDT")
494            .interval(BinanceKlineInterval::Hour1)
495            .start_time(1_700_000_000_000i64)
496            .limit(100u32)
497            .build()
498            .unwrap();
499
500        assert_eq!(params.symbol, "BTCUSDT");
501        assert_eq!(params.interval, BinanceKlineInterval::Hour1);
502        assert_eq!(params.start_time, Some(1_700_000_000_000));
503        assert_eq!(params.limit, Some(100));
504    }
505
506    #[rstest]
507    fn test_trades_params_builder() {
508        let params = BinanceTradesParamsBuilder::default()
509            .symbol("ETHUSDT")
510            .limit(500u32)
511            .build()
512            .unwrap();
513
514        assert_eq!(params.symbol, "ETHUSDT");
515        assert_eq!(params.limit, Some(500));
516    }
517
518    #[rstest]
519    fn test_depth_params_builder() {
520        let params = BinanceDepthParamsBuilder::default()
521            .symbol("BTCUSDT")
522            .limit(100u32)
523            .build()
524            .unwrap();
525
526        assert_eq!(params.symbol, "BTCUSDT");
527        assert_eq!(params.limit, Some(100));
528    }
529
530    #[rstest]
531    fn test_ticker_params_serialization() {
532        let params = BinanceTicker24hrParams {
533            symbol: Some("BTCUSDT".to_string()),
534            symbols: None,
535            response_type: None,
536        };
537
538        let serialized = serde_urlencoded::to_string(&params).unwrap();
539        assert_eq!(serialized, "symbol=BTCUSDT");
540    }
541
542    #[rstest]
543    fn test_order_query_params_builder() {
544        let params = BinanceOrderQueryParamsBuilder::default()
545            .symbol("BTCUSDT")
546            .order_id(12345_i64)
547            .recv_window(5_000_u64)
548            .build()
549            .unwrap();
550
551        assert_eq!(params.symbol, "BTCUSDT");
552        assert_eq!(params.order_id, Some(12345));
553        assert_eq!(params.recv_window, Some(5_000));
554    }
555
556    #[rstest]
557    fn test_income_history_params_serialization() {
558        let params = BinanceIncomeHistoryParamsBuilder::default()
559            .symbol("ETHUSDT")
560            .income_type(BinanceIncomeType::FundingFee)
561            .limit(50_u32)
562            .build()
563            .unwrap();
564
565        let serialized = serde_urlencoded::to_string(&params).unwrap();
566        assert_eq!(serialized, "symbol=ETHUSDT&incomeType=FUNDING_FEE&limit=50");
567    }
568
569    #[rstest]
570    fn test_open_orders_params_builder() {
571        let params = BinanceOpenOrdersParamsBuilder::default()
572            .symbol("BNBUSDT")
573            .build()
574            .unwrap();
575
576        assert_eq!(params.symbol.as_deref(), Some("BNBUSDT"));
577        assert!(params.recv_window.is_none());
578    }
579}