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 }
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, #[serde(skip_serializing_if = "Option::is_none")]
225 pub recv_window: Option<usize>, }
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>, }
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>, }
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>, }
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}