binance_async_api/rest/
usdm.rs

1use super::Request;
2use crate::client::Product;
3use reqwest::Method;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, Default, Serialize)]
7pub struct ExchangeInfoRequest;
8
9#[derive(Debug, Clone, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct ExchangeInfoResponse {
12    pub exchange_filters: Vec<ExchangeFilter>,
13    pub rate_limits: Vec<RateLimit>,
14    pub server_time: usize,
15    pub assets: Vec<Asset>,
16    pub symbols: Vec<Market>,
17    pub timezone: String,
18}
19
20#[derive(Debug, Clone, Deserialize)]
21pub struct ExchangeFilter {
22    // No info about this on binance api docs
23}
24
25#[derive(Debug, Clone, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct RateLimit {
28    pub rate_limit_type: String,
29    pub interval: String,
30    pub interval_num: usize,
31    pub limit: usize,
32}
33
34#[derive(Debug, Clone, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct Asset {
37    pub asset: String,
38    pub margin_available: bool,
39    pub auto_asset_exchange: String,
40}
41
42#[derive(Debug, Clone, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct Market {
45    pub symbol: String,
46    pub pair: String,
47    pub contract_type: String,
48    pub delivery_date: usize,
49    pub onboard_date: usize,
50    pub status: String,
51    pub maint_margin_percent: String,
52    pub required_margin_percent: String,
53    pub base_asset: String,
54    pub quote_asset: String,
55    pub margin_asset: String,
56    pub price_precision: usize,
57    pub quantity_precision: usize,
58    pub base_asset_precision: usize,
59    pub quote_precision: usize,
60    pub underlying_type: String,
61    pub underlying_sub_type: Vec<String>,
62    pub trigger_protect: String,
63    pub filters: Vec<SymbolFilter>,
64    pub order_types: Vec<String>,
65    pub time_in_force: Vec<String>,
66    pub liquidation_fee: String,
67    pub market_take_bound: String,
68}
69
70#[derive(Debug, Clone, Deserialize)]
71#[serde(tag = "filterType", rename_all = "SCREAMING_SNAKE_CASE")]
72pub enum SymbolFilter {
73    #[serde(rename_all = "camelCase")]
74    PriceFilter {
75        min_price: String,
76        max_price: String,
77        tick_size: String,
78    },
79    #[serde(rename_all = "camelCase")]
80    LotSize {
81        min_qty: String,
82        max_qty: String,
83        step_size: String,
84    },
85    #[serde(rename_all = "camelCase")]
86    MarketLotSize {
87        min_qty: String,
88        max_qty: String,
89        step_size: String,
90    },
91    MaxNumOrders {
92        limit: usize,
93    },
94    MaxNumAlgoOrders {
95        limit: usize,
96    },
97    #[serde(rename_all = "camelCase")]
98    PercentPrice {
99        multiplier_up: String,
100        multiplier_down: String,
101        multiplier_decimal: String,
102    },
103    MinNotional {
104        notional: String,
105    },
106}
107
108impl Request for ExchangeInfoRequest {
109    const PRODUCT: Product = Product::UsdMFutures;
110    const METHOD: Method = Method::GET;
111    const ENDPOINT: &'static str = "/fapi/v1/exchangeInfo";
112    type Response = ExchangeInfoResponse;
113}
114
115#[derive(Debug, Clone, Copy, Default, Serialize)]
116pub struct OrderBookRequest<'a> {
117    pub symbol: &'a str,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub limit: Option<usize>,
120}
121
122#[derive(Debug, Clone, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub struct OrderBookResponse {
125    pub last_update_id: usize,
126    #[serde(rename = "E")]
127    pub message_output_time: usize,
128    #[serde(rename = "T")]
129    pub transaction_time: usize,
130    pub bids: Vec<BookLevel>,
131    pub asks: Vec<BookLevel>,
132}
133
134#[derive(Debug, Clone, Deserialize)]
135pub struct BookLevel {
136    pub price: String,
137    pub qty: String,
138}
139
140impl<'a> Request for OrderBookRequest<'a> {
141    const PRODUCT: Product = Product::UsdMFutures;
142    const METHOD: Method = Method::GET;
143    const ENDPOINT: &'static str = "/fapi/v1/depth";
144    type Response = OrderBookResponse;
145}
146
147#[derive(Debug, Clone, Copy, Default, Serialize)]
148pub struct PriceTickerRequest<'a> {
149    pub symbol: &'a str,
150}
151
152#[derive(Debug, Clone, Deserialize)]
153pub struct PriceTickerResponse {
154    pub symbol: String,
155    pub price: String,
156    pub time: usize,
157}
158
159impl<'a> Request for PriceTickerRequest<'a> {
160    const PRODUCT: Product = Product::UsdMFutures;
161    const METHOD: Method = Method::GET;
162    const ENDPOINT: &'static str = "/fapi/v1/ticker/price";
163    type Response = PriceTickerResponse;
164}
165
166#[derive(Debug, Clone, Copy, Default, Serialize)]
167pub struct BookTickerRequest<'a> {
168    pub symbol: &'a str,
169}
170
171#[derive(Debug, Clone, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct BookTickerResponse {
174    pub symbol: String,
175    pub bid_price: String,
176    pub bid_qty: String,
177    pub ask_price: String,
178    pub ask_qty: String,
179    pub time: usize,
180}
181
182impl<'a> Request for BookTickerRequest<'a> {
183    const PRODUCT: Product = Product::UsdMFutures;
184    const METHOD: Method = Method::GET;
185    const ENDPOINT: &'static str = "/fapi/v1/ticker/bookTicker";
186    type Response = BookTickerResponse;
187}
188
189#[derive(Debug, Clone, Copy, Default, Serialize)]
190pub struct CreateListenKeyRequest {}
191
192#[derive(Debug, Clone, Deserialize)]
193#[serde(rename_all = "camelCase")]
194pub struct CreateListenKeyResponse {
195    pub listen_key: String,
196}
197
198impl Request for CreateListenKeyRequest {
199    const PRODUCT: Product = Product::UsdMFutures;
200    const METHOD: Method = Method::POST;
201    const ENDPOINT: &'static str = "/fapi/v1/listenKey";
202    const KEYED: bool = true;
203    type Response = CreateListenKeyResponse;
204}
205
206#[derive(Debug, Clone, Copy, Default, Serialize)]
207pub struct KeepAliveListenKeyRequest {}
208
209#[derive(Debug, Clone, Deserialize)]
210pub struct KeepAliveListenKeyResponse {}
211
212impl Request for KeepAliveListenKeyRequest {
213    const PRODUCT: Product = Product::UsdMFutures;
214    const METHOD: Method = Method::PUT;
215    const ENDPOINT: &'static str = "/fapi/v1/listenKey";
216    const KEYED: bool = true;
217    type Response = KeepAliveListenKeyResponse;
218}
219
220#[derive(Debug, Clone, Copy, Default, Serialize)]
221#[serde(rename_all = "camelCase")]
222pub struct ChangePositionModeRequest<'a> {
223    pub dual_side_position: &'a str, // true or false
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub recv_window: Option<usize>, // <= 60_000
226}
227
228#[derive(Debug, Clone, Deserialize)]
229pub struct ChangePositionModeResponse {}
230
231impl<'a> Request for ChangePositionModeRequest<'a> {
232    const PRODUCT: Product = Product::UsdMFutures;
233    const METHOD: Method = Method::POST;
234    const ENDPOINT: &'static str = "/fapi/v1/positionSide/dual";
235    const KEYED: bool = true;
236    const SIGNED: bool = true;
237    type Response = ChangePositionModeResponse;
238}
239
240#[derive(Debug, Clone, Copy, Default, Serialize)]
241#[serde(rename_all = "camelCase")]
242pub struct NewOrderRequest<'a> {
243    pub symbol: &'a str,
244    pub side: &'a str,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub position_side: Option<&'a str>,
247    pub r#type: &'a str,
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub time_in_force: Option<&'a str>,
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub quantity: Option<&'a str>,
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub reduce_only: Option<bool>,
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub price: Option<&'a str>,
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub new_client_order_id: Option<&'a str>,
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub stop_price: Option<&'a str>,
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub close_position: Option<bool>,
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub activation_price: Option<&'a str>,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub callback_rate: Option<&'a str>,
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub working_type: Option<&'a str>,
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub price_protect: Option<bool>,
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub self_trade_prevention_mode: Option<&'a str>,
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub good_till_date: Option<usize>,
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub recv_window: Option<usize>, // <= 60_000
276}
277
278#[derive(Debug, Clone, Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct NewOrderResponse {
281    pub client_order_id: String,
282    pub cum_qty: String,
283    pub cum_quote: String,
284    pub executed_qty: String,
285    pub order_id: usize,
286    pub avg_price: String,
287    pub orig_qty: String,
288    pub price: String,
289    pub reduce_only: bool,
290    pub side: String,
291    pub position_side: String,
292    pub status: String,
293    pub stop_price: String,
294    pub close_position: bool,
295    pub symbol: String,
296    pub time_in_force: String,
297    pub r#type: String,
298    pub orig_type: String,
299    pub activate_price: Option<String>,
300    pub price_rate: Option<String>,
301    pub update_time: usize,
302    pub working_type: String,
303    pub price_protect: bool,
304    pub self_trade_prevention_mode: String,
305    pub good_till_date: usize,
306}
307
308impl<'a> Request for NewOrderRequest<'a> {
309    const PRODUCT: Product = Product::UsdMFutures;
310    const METHOD: Method = Method::POST;
311    const ENDPOINT: &'static str = "/fapi/v1/order";
312    const KEYED: bool = true;
313    const SIGNED: bool = true;
314    type Response = NewOrderResponse;
315}
316
317#[derive(Debug, Clone, Copy, Default, Serialize)]
318#[serde(rename_all = "camelCase")]
319pub struct CancelOrderRequest<'a> {
320    pub symbol: &'a str,
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub order_id: Option<usize>,
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub orig_client_order_id: Option<&'a str>,
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub recv_window: Option<usize>, // <= 60_000
327}
328
329#[derive(Debug, Clone, Deserialize)]
330#[serde(rename_all = "camelCase")]
331pub struct CancelOrderResponse {
332    pub client_order_id: String,
333    pub cum_qty: String,
334    pub cum_quote: String,
335    pub executed_qty: String,
336    pub order_id: usize,
337    pub orig_qty: String,
338    pub orig_type: String,
339    pub price: String,
340    pub reduce_only: bool,
341    pub side: String,
342    pub position_side: String,
343    pub status: String,
344    pub stop_price: String,
345    pub close_position: bool,
346    pub symbol: String,
347    pub time_in_force: String,
348    pub r#type: String,
349    pub activate_price: Option<String>,
350    pub price_rate: Option<String>,
351    pub update_time: usize,
352    pub working_type: String,
353    pub price_protect: bool,
354    pub self_trade_prevention_mode: String,
355    pub good_till_date: usize,
356}
357
358impl<'a> Request for CancelOrderRequest<'a> {
359    const PRODUCT: Product = Product::UsdMFutures;
360    const METHOD: Method = Method::DELETE;
361    const ENDPOINT: &'static str = "/fapi/v1/order";
362    const KEYED: bool = true;
363    const SIGNED: bool = true;
364    type Response = CancelOrderResponse;
365}
366
367#[derive(Debug, Clone, Copy, Default, Serialize)]
368#[serde(rename_all = "camelCase")]
369pub struct UserCommissionRateRequest<'a> {
370    pub symbol: &'a str,
371    #[serde(skip_serializing_if = "Option::is_none")]
372    pub recv_window: Option<usize>, // <= 60_000
373}
374
375#[derive(Debug, Clone, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct UserCommissionRateResponse {
378    pub symbol: String,
379    pub maker_commission_rate: String,
380    pub taker_commission_rate: String,
381}
382
383impl<'a> Request for UserCommissionRateRequest<'a> {
384    const PRODUCT: Product = Product::UsdMFutures;
385    const METHOD: Method = Method::GET;
386    const ENDPOINT: &'static str = "/fapi/v1/commissionRate";
387    const KEYED: bool = true;
388    const SIGNED: bool = true;
389    type Response = UserCommissionRateResponse;
390}
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395    use crate::client;
396    use tokio_test::block_on;
397
398    #[test]
399    fn test_exchange_info_request() {
400        let client = client::BinanceClient::new();
401        let req = ExchangeInfoRequest;
402        let res = block_on(client.request(req, None, None))
403            .expect("Failed to get exchange info");
404        assert!(res.status_code.is_success());
405    }
406
407    #[test]
408    fn test_order_book_request() {
409        let client = client::BinanceClient::new();
410        let req = OrderBookRequest {
411            symbol: "BTCUSDT",
412            limit: Some(5),
413        };
414        let res = block_on(client.request(req, None, None))
415            .expect("Failed to get order book");
416        assert!(res.status_code.is_success());
417    }
418
419    #[test]
420    fn test_price_ticker_request() {
421        let client = client::BinanceClient::new();
422        let req = PriceTickerRequest { symbol: "BTCUSDT" };
423        let res = block_on(client.request(req, None, None))
424            .expect("Failed to get price ticker");
425        assert!(res.status_code.is_success());
426    }
427
428    #[test]
429    fn test_book_ticker_request() {
430        let client = client::BinanceClient::new();
431        let req = BookTickerRequest { symbol: "BTCUSDT" };
432        let res = block_on(client.request(req, None, None))
433            .expect("Failed to get book ticker");
434        assert!(res.status_code.is_success());
435    }
436}