1use crate::{
2 Error, Timestamp,
3 crypto::{SensitiveString, Signer},
4 http::{
5 APIErrorResponse, APIKeyInformation, AccountInfo, AmendOrderBatchRequest,
6 AmendOrderBatchResult, AmendOrderRequest, AmendOrderResponse, CancelAllOrdersRequest,
7 CancelAllOrdersResponse, CancelOrderBatchRequest, CancelOrderBatchResult,
8 CancelOrderRequest, CancelOrderResponse, ClosedPnl, CursorPagination, EmptyResult,
9 ExecutionEntry, GetClosedPnlParams, GetExecutionListParams, GetInstrumentsInfoParams,
10 GetKLinesParams, GetOpenClosedOrdersParams, GetOrderHistoryParams, GetPositionInfoParams,
11 GetTickersParams, GetTradesParams, GetTransactionLogParams, GetWalletBalanceParams,
12 Headers, InstrumentsInfo, KLine, List, Order, PlaceOrderBatchRequest,
13 PlaceOrderBatchResult, PlaceOrderRequest, PlaceOrderResponse, Position, Resp, Response,
14 ServerTime, SetAutoAddMarginRequest, SetLeverageRequest, SetRiskLimitRequest,
15 SetRiskLimitResponse, SetTradingStopRequest, SwitchCrossIsolatedMarginRequest,
16 SwitchPositionModeRequest, Ticker, Trade, TransactionLog, WalletBalance,
17 },
18 serde::{deserialize_json, serialize_json, serialize_query},
19 url::*,
20};
21use reqwest::{self, Method, RequestBuilder, header::HeaderMap};
22
23pub struct Config {
24 pub base_url: String,
25 pub api_key: Option<SensitiveString>,
26 pub api_secret: Option<SensitiveString>,
27 pub recv_window: Timestamp,
29 pub referer: Option<String>,
31}
32
33#[derive(Debug)]
35pub struct Client {
36 base_url: String,
37 headers: HeaderMap,
38 client: reqwest::Client,
39 signer: Option<Signer>,
40}
41
42impl Client {
43 pub fn new(cfg: Config) -> Result<Self, Error> {
44 let mut headers = HeaderMap::new();
45
46 if let Some(api_key) = cfg.api_key.as_ref() {
47 let api_key = api_key.expose().parse()?;
48 headers.append(HEADER_X_BAPI_API_KEY, api_key);
49 }
50
51 let recv_window = cfg.recv_window.to_string().parse()?;
52 headers.append(HEADER_X_BAPI_RECV_WINDOW, recv_window);
53
54 if let Some(referer) = cfg.referer {
55 let referer = referer.parse()?;
56 headers.append(HEADER_X_REFERER, referer);
57 }
58
59 let signer = cfg
60 .api_secret
61 .map(|api_secret| -> Result<Signer, Error> {
62 let api_key = cfg
63 .api_key
64 .ok_or_else(|| Error::from("api_key is required when api_secret is set"))?;
65 Ok(Signer::new(api_key, api_secret, cfg.recv_window, None))
66 })
67 .transpose()?;
68
69 Ok(Self {
70 base_url: cfg.base_url,
71 headers,
72 client: reqwest::Client::builder().build()?,
73 signer,
74 })
75 }
76
77 fn get_signed_headers(&self, s: &str) -> HeaderMap {
78 let mut headers = self.headers.clone();
79
80 let (signature, timestamp) = self.signer.as_ref().unwrap().sign(s);
81 let signature = signature.parse().unwrap();
82 headers.append(HEADER_X_BAPI_SIGN, signature);
83 let timestamp = timestamp.parse().unwrap();
84 headers.append(HEADER_X_BAPI_TIMESTAMP, timestamp);
85
86 headers
87 }
88}
89
90impl Client {
92 #[tracing::instrument(skip(self), err)]
93 pub async fn get_server_time(&self) -> Result<Response<ServerTime>, Error> {
94 let url = format!("{}{}", self.base_url, Path::MarketServerTime);
95
96 let request = self.client.request(Method::GET, url);
97
98 let response = send(request).await?;
99 Ok(response)
100 }
101
102 #[tracing::instrument(skip(self), err)]
103 pub async fn get_kline(&self, params: GetKLinesParams) -> Result<Response<KLine>, Error> {
104 let url = format!("{}{}", self.base_url, Path::MarketKline);
105
106 let request = self.client.request(Method::GET, url).query(¶ms);
107
108 let response = send(request).await?;
109 Ok(response)
110 }
111
112 #[tracing::instrument(skip(self), err)]
116 pub async fn get_tickers(&self, params: GetTickersParams) -> Result<Response<Ticker>, Error> {
117 let url = format!("{}{}", self.base_url, Path::MarketTickers);
118
119 let request = self.client.request(Method::GET, url).query(¶ms);
120
121 let response = send(request).await?;
122 Ok(response)
123 }
124
125 #[tracing::instrument(skip(self), err)]
126 pub async fn get_instruments_info(
127 &self,
128 params: GetInstrumentsInfoParams,
129 ) -> Result<Response<InstrumentsInfo>, Error> {
130 let url = format!("{}{}", self.base_url, Path::MarketInstrumentsInfo);
131
132 let request = self.client.request(Method::GET, url).query(¶ms);
133
134 let response = send(request).await?;
135 Ok(response)
136 }
137
138 #[tracing::instrument(skip(self), err)]
139 pub async fn get_public_recent_trading_history(
140 &self,
141 params: GetTradesParams,
142 ) -> Result<Response<Trade>, Error> {
143 let url = format!("{}{}", self.base_url, Path::MarketRecentTrade);
144
145 let request = self.client.request(Method::GET, url).query(¶ms);
146
147 let response = send(request).await?;
148 Ok(response)
149 }
150}
151
152impl Client {
154 #[tracing::instrument(skip(self), err)]
201 pub async fn place_order(
202 &self,
203 request: PlaceOrderRequest,
204 ) -> Result<Response<PlaceOrderResponse>, Error> {
205 let url = format!("{}{}", self.base_url, Path::TradeOrderCreate);
206 let json = serialize_json(&request)?;
207 let headers = self.get_signed_headers(&json);
208
209 let request = self
210 .client
211 .request(Method::POST, url)
212 .headers(headers)
213 .body(json);
214
215 let response = send(request).await?;
216 Ok(response)
217 }
218
219 #[tracing::instrument(skip(self), err)]
223 pub async fn amend_order(
224 &self,
225 request: AmendOrderRequest,
226 ) -> Result<Response<AmendOrderResponse>, Error> {
227 let url = format!("{}{}", self.base_url, Path::TradeOrderAmend);
228 let json = serialize_json(&request)?;
229 let headers = self.get_signed_headers(&json);
230
231 let request = self
232 .client
233 .request(Method::POST, url)
234 .headers(headers)
235 .body(json);
236
237 let response = send(request).await?;
238 Ok(response)
239 }
240
241 #[tracing::instrument(skip(self), err)]
247 pub async fn cancel_order(
248 &self,
249 request: CancelOrderRequest,
250 ) -> Result<Response<CancelOrderResponse>, Error> {
251 let url = format!("{}{}", self.base_url, Path::TradeOrderCancel);
252 let json = serialize_json(&request)?;
253 let headers = self.get_signed_headers(&json);
254
255 let request = self
256 .client
257 .request(Method::POST, url)
258 .headers(headers)
259 .body(json);
260
261 let response = send(request).await?;
262 Ok(response)
263 }
264
265 #[tracing::instrument(skip(self), err)]
279 pub async fn get_open_closed_orders(
280 &self,
281 params: GetOpenClosedOrdersParams,
282 ) -> Result<Response<CursorPagination<Order>>, Error> {
283 let query = serialize_query(¶ms)?;
284 let url = format!("{}{}?{query}", self.base_url, Path::TradeOrderRealtime);
285 let headers = self.get_signed_headers(&query);
286
287 let request = self.client.request(Method::GET, url).headers(headers);
288
289 let response = send(request).await?;
290 Ok(response)
291 }
292
293 #[tracing::instrument(skip(self), err)]
297 pub async fn get_open_closed_orders_all(
298 &self,
299 params: GetOpenClosedOrdersParams,
300 ) -> Result<Vec<Order>, Error> {
301 let mut all = Vec::new();
302 let mut p = params;
303 loop {
304 let page = self.get_open_closed_orders(p.clone()).await?;
305 all.extend(page.result.list);
306 match page.result.next_page_cursor {
307 Some(cursor) => p = p.with_cursor(cursor),
308 None => break,
309 }
310 }
311 Ok(all)
312 }
313
314 #[tracing::instrument(skip(self), err)]
317 pub async fn cancel_all_orders(
318 &self,
319 request: CancelAllOrdersRequest,
320 ) -> Result<Response<CancelAllOrdersResponse>, Error> {
321 let url = format!("{}{}", self.base_url, Path::TradeOrderCancelAll);
322 let json = serialize_json(&request)?;
323 let headers = self.get_signed_headers(&json);
324
325 let request = self
326 .client
327 .request(Method::POST, url)
328 .headers(headers)
329 .body(json);
330
331 let response = send(request).await?;
332 Ok(response)
333 }
334
335 #[tracing::instrument(skip(self), err)]
339 pub async fn get_order_history(
340 &self,
341 params: GetOrderHistoryParams,
342 ) -> Result<Response<CursorPagination<Order>>, Error> {
343 let query = serialize_query(¶ms)?;
344 let url = format!("{}{}?{query}", self.base_url, Path::TradeOrderHistory);
345 let headers = self.get_signed_headers(&query);
346
347 let request = self.client.request(Method::GET, url).headers(headers);
348
349 let response = send(request).await?;
350 Ok(response)
351 }
352
353 #[tracing::instrument(skip(self), err)]
355 pub async fn get_order_history_all(
356 &self,
357 params: GetOrderHistoryParams,
358 ) -> Result<Vec<Order>, Error> {
359 let mut all = Vec::new();
360 let mut p = params;
361 loop {
362 let page = self.get_order_history(p.clone()).await?;
363 all.extend(page.result.list);
364 match page.result.next_page_cursor {
365 Some(cursor) => p = p.with_cursor(cursor),
366 None => break,
367 }
368 }
369 Ok(all)
370 }
371
372 #[tracing::instrument(skip(self), err)]
376 pub async fn place_orders_batch(
377 &self,
378 request: PlaceOrderBatchRequest,
379 ) -> Result<Response<List<PlaceOrderBatchResult>>, Error> {
380 let url = format!("{}{}", self.base_url, Path::TradeOrderCreateBatch);
381 let json = serialize_json(&request)?;
382 let headers = self.get_signed_headers(&json);
383
384 let request = self
385 .client
386 .request(Method::POST, url)
387 .headers(headers)
388 .body(json);
389
390 let response = send(request).await?;
391 Ok(response)
392 }
393
394 #[tracing::instrument(skip(self), err)]
398 pub async fn amend_orders_batch(
399 &self,
400 request: AmendOrderBatchRequest,
401 ) -> Result<Response<List<AmendOrderBatchResult>>, Error> {
402 let url = format!("{}{}", self.base_url, Path::TradeOrderAmendBatch);
403 let json = serialize_json(&request)?;
404 let headers = self.get_signed_headers(&json);
405
406 let request = self
407 .client
408 .request(Method::POST, url)
409 .headers(headers)
410 .body(json);
411
412 let response = send(request).await?;
413 Ok(response)
414 }
415
416 #[tracing::instrument(skip(self), err)]
420 pub async fn cancel_orders_batch(
421 &self,
422 request: CancelOrderBatchRequest,
423 ) -> Result<Response<List<CancelOrderBatchResult>>, Error> {
424 let url = format!("{}{}", self.base_url, Path::TradeOrderCancelBatch);
425 let json = serialize_json(&request)?;
426 let headers = self.get_signed_headers(&json);
427
428 let request = self
429 .client
430 .request(Method::POST, url)
431 .headers(headers)
432 .body(json);
433
434 let response = send(request).await?;
435 Ok(response)
436 }
437}
438
439impl Client {
441 #[tracing::instrument(skip(self), err)]
452 pub async fn get_position_info(
453 &self,
454 params: GetPositionInfoParams,
455 ) -> Result<Response<CursorPagination<Position>>, Error> {
456 let query = serialize_query(¶ms)?;
457 let url = format!("{}{}?{query}", self.base_url, Path::PositionList);
458 let headers = self.get_signed_headers(&query);
459
460 let request = self.client.request(Method::GET, url).headers(headers);
461
462 let response = send(request).await?;
463 Ok(response)
464 }
465
466 #[tracing::instrument(skip(self), err)]
470 pub async fn get_position_info_all(
471 &self,
472 params: GetPositionInfoParams,
473 ) -> Result<Vec<Position>, Error> {
474 let mut all = Vec::new();
475 let mut p = params;
476 loop {
477 let page = self.get_position_info(p.clone()).await?;
478 all.extend(page.result.list);
479 match page.result.next_page_cursor {
480 Some(cursor) => p = p.with_cursor(cursor),
481 None => break,
482 }
483 }
484 Ok(all)
485 }
486
487 #[tracing::instrument(skip(self), err)]
490 pub async fn set_leverage(
491 &self,
492 request: SetLeverageRequest,
493 ) -> Result<Response<EmptyResult>, Error> {
494 let url = format!("{}{}", self.base_url, Path::PositionSetLeverage);
495 let json = serialize_json(&request)?;
496 let headers = self.get_signed_headers(&json);
497
498 let request = self
499 .client
500 .request(Method::POST, url)
501 .headers(headers)
502 .body(json);
503
504 let response = send(request).await?;
505 Ok(response)
506 }
507
508 #[tracing::instrument(skip(self), err)]
511 pub async fn set_trading_stop(
512 &self,
513 request: SetTradingStopRequest,
514 ) -> Result<Response<EmptyResult>, Error> {
515 let url = format!("{}{}", self.base_url, Path::PositionTradingStop);
516 let json = serialize_json(&request)?;
517 let headers = self.get_signed_headers(&json);
518
519 let request = self
520 .client
521 .request(Method::POST, url)
522 .headers(headers)
523 .body(json);
524
525 let response = send(request).await?;
526 Ok(response)
527 }
528
529 #[tracing::instrument(skip(self), err)]
532 pub async fn switch_cross_isolated_margin(
533 &self,
534 request: SwitchCrossIsolatedMarginRequest,
535 ) -> Result<Response<EmptyResult>, Error> {
536 let url = format!("{}{}", self.base_url, Path::PositionSwitchIsolated);
537 let json = serialize_json(&request)?;
538 let headers = self.get_signed_headers(&json);
539
540 let request = self
541 .client
542 .request(Method::POST, url)
543 .headers(headers)
544 .body(json);
545
546 let response = send(request).await?;
547 Ok(response)
548 }
549
550 #[tracing::instrument(skip(self), err)]
553 pub async fn switch_position_mode(
554 &self,
555 request: SwitchPositionModeRequest,
556 ) -> Result<Response<EmptyResult>, Error> {
557 let url = format!("{}{}", self.base_url, Path::PositionSwitchMode);
558 let json = serialize_json(&request)?;
559 let headers = self.get_signed_headers(&json);
560
561 let request = self
562 .client
563 .request(Method::POST, url)
564 .headers(headers)
565 .body(json);
566
567 let response = send(request).await?;
568 Ok(response)
569 }
570
571 #[tracing::instrument(skip(self), err)]
574 pub async fn set_auto_add_margin(
575 &self,
576 request: SetAutoAddMarginRequest,
577 ) -> Result<Response<EmptyResult>, Error> {
578 let url = format!("{}{}", self.base_url, Path::PositionSetAutoAddMargin);
579 let json = serialize_json(&request)?;
580 let headers = self.get_signed_headers(&json);
581
582 let request = self
583 .client
584 .request(Method::POST, url)
585 .headers(headers)
586 .body(json);
587
588 let response = send(request).await?;
589 Ok(response)
590 }
591
592 #[tracing::instrument(skip(self), err)]
595 pub async fn set_risk_limit(
596 &self,
597 request: SetRiskLimitRequest,
598 ) -> Result<Response<SetRiskLimitResponse>, Error> {
599 let url = format!("{}{}", self.base_url, Path::PositionSetRiskLimit);
600 let json = serialize_json(&request)?;
601 let headers = self.get_signed_headers(&json);
602
603 let request = self
604 .client
605 .request(Method::POST, url)
606 .headers(headers)
607 .body(json);
608
609 let response = send(request).await?;
610 Ok(response)
611 }
612
613 #[tracing::instrument(skip(self), err)]
616 pub async fn get_closed_pnl(
617 &self,
618 params: GetClosedPnlParams,
619 ) -> Result<Response<CursorPagination<ClosedPnl>>, Error> {
620 let query = serialize_query(¶ms)?;
621 let url = format!("{}{}?{query}", self.base_url, Path::PositionClosedPnl);
622 let headers = self.get_signed_headers(&query);
623
624 let request = self.client.request(Method::GET, url).headers(headers);
625
626 let response = send(request).await?;
627 Ok(response)
628 }
629
630 #[tracing::instrument(skip(self), err)]
632 pub async fn get_closed_pnl_all(
633 &self,
634 params: GetClosedPnlParams,
635 ) -> Result<Vec<ClosedPnl>, Error> {
636 let mut all = Vec::new();
637 let mut p = params;
638 loop {
639 let page = self.get_closed_pnl(p.clone()).await?;
640 all.extend(page.result.list);
641 match page.result.next_page_cursor {
642 Some(cursor) => p = p.with_cursor(cursor),
643 None => break,
644 }
645 }
646 Ok(all)
647 }
648
649 #[tracing::instrument(skip(self), err)]
652 pub async fn get_execution_list(
653 &self,
654 params: GetExecutionListParams,
655 ) -> Result<Response<CursorPagination<ExecutionEntry>>, Error> {
656 let query = serialize_query(¶ms)?;
657 let url = format!("{}{}?{query}", self.base_url, Path::ExecutionList);
658 let headers = self.get_signed_headers(&query);
659
660 let request = self.client.request(Method::GET, url).headers(headers);
661
662 let response = send(request).await?;
663 Ok(response)
664 }
665
666 #[tracing::instrument(skip(self), err)]
668 pub async fn get_execution_list_all(
669 &self,
670 params: GetExecutionListParams,
671 ) -> Result<Vec<ExecutionEntry>, Error> {
672 let mut all = Vec::new();
673 let mut p = params;
674 loop {
675 let page = self.get_execution_list(p.clone()).await?;
676 all.extend(page.result.list);
677 match page.result.next_page_cursor {
678 Some(cursor) => p = p.with_cursor(cursor),
679 None => break,
680 }
681 }
682 Ok(all)
683 }
684}
685
686impl Client {
688 #[tracing::instrument(skip(self), err)]
690 pub async fn get_wallet_balance(
691 &self,
692 params: GetWalletBalanceParams,
693 ) -> Result<Response<List<WalletBalance>>, Error> {
694 let query = serialize_query(¶ms)?;
695 let url = format!("{}{}?{query}", self.base_url, Path::AccountWalletBalance);
696 let headers = self.get_signed_headers(&query);
697
698 let request = self.client.request(Method::GET, url).headers(headers);
699
700 let response = send(request).await?;
701 Ok(response)
702 }
703
704 #[tracing::instrument(skip(self), err)]
707 pub async fn get_transaction_log(
708 &self,
709 params: GetTransactionLogParams,
710 ) -> Result<Response<CursorPagination<TransactionLog>>, Error> {
711 let query = serialize_query(¶ms)?;
712 let url = format!("{}{}?{query}", self.base_url, Path::AccountTransactionLog);
713 let headers = self.get_signed_headers(&query);
714
715 let request = self.client.request(Method::GET, url).headers(headers);
716
717 let response = send(request).await?;
718 Ok(response)
719 }
720
721 #[tracing::instrument(skip(self), err)]
725 pub async fn get_transaction_log_all(
726 &self,
727 params: GetTransactionLogParams,
728 ) -> Result<Vec<TransactionLog>, Error> {
729 let mut all = Vec::new();
730 let mut p = params;
731 loop {
732 let page = self.get_transaction_log(p.clone()).await?;
733 all.extend(page.result.list);
734 match page.result.next_page_cursor {
735 Some(cursor) => p = p.with_cursor(cursor),
736 None => break,
737 }
738 }
739 Ok(all)
740 }
741
742 #[tracing::instrument(skip(self), err)]
744 pub async fn get_account_info(&self) -> Result<Response<AccountInfo>, Error> {
745 let url = format!("{}{}", self.base_url, Path::AccountInfo);
746 let query = "";
747 let headers = self.get_signed_headers(query);
748
749 let request = self.client.request(Method::GET, url).headers(headers);
750
751 let response = send(request).await?;
752 Ok(response)
753 }
754}
755
756impl Client {
758 #[tracing::instrument(skip(self), err)]
761 pub async fn get_api_key_information(&self) -> Result<Response<APIKeyInformation>, Error> {
762 let url = format!("{}{}", self.base_url, Path::UserQueryApi);
763 let query = "";
764 let headers = self.get_signed_headers(query);
765
766 let request = self.client.request(Method::GET, url).headers(headers);
767
768 let response = send(request).await?;
769 Ok(response)
770 }
771}
772
773async fn send<T>(request: RequestBuilder) -> Result<Response<T>, Error>
774where
775 T: serde::de::DeserializeOwned,
776{
777 let start = std::time::Instant::now();
778 let response = request.send().await?;
779 let elapsed_ms = start.elapsed().as_millis();
780 let headers = parse_headers(response.headers());
781 let json = response.text().await?;
782 if !headers.is_ret_code_ok() {
783 let msg: APIErrorResponse = deserialize_json(&json)?;
784 tracing::debug!(elapsed_ms, ret_code = msg.ret_code, "api error response");
785 return Err(msg.into());
786 }
787
788 tracing::debug!(
789 elapsed_ms,
790 api_limit = headers.api_limit,
791 api_limit_status = headers.api_limit_status,
792 "api call completed"
793 );
794 let response: Resp<_> = deserialize_json(&json)?;
795 let response = Response {
796 result: response.result,
797 time: response.time,
798 headers,
799 ret_ext_info: response.ret_ext_info,
800 };
801 Ok(response)
802}
803
804fn parse_headers(headers: &HeaderMap) -> Headers {
806 let ret_code = headers
807 .get(HEADER_RET_CODE)
808 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
809 let trace_id = headers
810 .get(HEADER_TRACE_ID)
811 .and_then(|h| h.to_str().map(|str| str.into()).ok());
812 let time_now = headers
813 .get(HEADER_TIME_NOW)
814 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
815
816 let api_limit = headers
817 .get(HEADER_X_BAPI_LIMIT)
818 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
819 let api_limit_status = headers
820 .get(HEADER_X_BAPI_LIMIT_STATUS)
821 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
822 let api_limit_reset_timestamp = headers
823 .get(HEADER_X_BAPI_LIMIT_RESET_TIMESTAMP)
824 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
825
826 Headers {
827 ret_code,
828 trace_id,
829 time_now,
830 api_limit,
831 api_limit_status,
832 api_limit_reset_timestamp,
833 }
834}