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, GetOrderbookParams,
11 GetPositionInfoParams, GetTickersParams, GetTradesParams, GetTransactionLogParams,
12 GetWalletBalanceParams, Headers, InstrumentsInfo, KLine, List, Order, Orderbook,
13 PlaceOrderBatchRequest, PlaceOrderBatchResult, PlaceOrderRequest, PlaceOrderResponse,
14 Position, Resp, Response, ServerTime, SetAutoAddMarginRequest, SetLeverageRequest,
15 SetRiskLimitRequest, SetRiskLimitResponse, SetTradingStopRequest,
16 SwitchCrossIsolatedMarginRequest, SwitchPositionModeRequest, Ticker, Trade, TransactionLog,
17 WalletBalance,
18 },
19 serde::{deserialize_json, serialize_json, serialize_query},
20 url::*,
21};
22use reqwest::{self, Method, RequestBuilder, header::HeaderMap};
23
24pub struct Config {
25 pub base_url: String,
26 pub api_key: Option<SensitiveString>,
27 pub api_secret: Option<SensitiveString>,
28 pub recv_window: Timestamp,
30 pub referer: Option<String>,
32}
33
34#[derive(Debug)]
36pub struct Client {
37 base_url: String,
38 headers: HeaderMap,
39 client: reqwest::Client,
40 signer: Option<Signer>,
41}
42
43impl Client {
44 pub fn new(cfg: Config) -> Result<Self, Error> {
45 let mut headers = HeaderMap::new();
46
47 if let Some(api_key) = cfg.api_key.as_ref() {
48 let api_key = api_key.expose().parse()?;
49 headers.append(HEADER_X_BAPI_API_KEY, api_key);
50 }
51
52 let recv_window = cfg.recv_window.to_string().parse()?;
53 headers.append(HEADER_X_BAPI_RECV_WINDOW, recv_window);
54
55 if let Some(referer) = cfg.referer {
56 let referer = referer.parse()?;
57 headers.append(HEADER_X_REFERER, referer);
58 }
59
60 let signer = cfg
61 .api_secret
62 .map(|api_secret| -> Result<Signer, Error> {
63 let api_key = cfg
64 .api_key
65 .ok_or_else(|| Error::from("api_key is required when api_secret is set"))?;
66 Ok(Signer::new(api_key, api_secret, cfg.recv_window, None))
67 })
68 .transpose()?;
69
70 Ok(Self {
71 base_url: cfg.base_url,
72 headers,
73 client: reqwest::Client::builder().build()?,
74 signer,
75 })
76 }
77
78 fn get_signed_headers(&self, s: &str) -> HeaderMap {
79 let mut headers = self.headers.clone();
80
81 let (signature, timestamp) = self.signer.as_ref().unwrap().sign(s);
82 let signature = signature.parse().unwrap();
83 headers.append(HEADER_X_BAPI_SIGN, signature);
84 let timestamp = timestamp.parse().unwrap();
85 headers.append(HEADER_X_BAPI_TIMESTAMP, timestamp);
86
87 headers
88 }
89}
90
91impl Client {
93 #[tracing::instrument(skip(self), err)]
94 pub async fn get_server_time(&self) -> Result<Response<ServerTime>, Error> {
95 let url = format!("{}{}", self.base_url, Path::MarketServerTime);
96
97 let request = self.client.request(Method::GET, url);
98
99 let response = send(request).await?;
100 Ok(response)
101 }
102
103 #[tracing::instrument(skip(self), err)]
104 pub async fn get_kline(&self, params: &GetKLinesParams) -> Result<Response<KLine>, Error> {
105 let url = format!("{}{}", self.base_url, Path::MarketKline);
106
107 let request = self.client.request(Method::GET, url).query(params);
108
109 let response = send(request).await?;
110 Ok(response)
111 }
112
113 #[tracing::instrument(skip(self), err)]
117 pub async fn get_tickers(&self, params: &GetTickersParams) -> Result<Response<Ticker>, Error> {
118 let url = format!("{}{}", self.base_url, Path::MarketTickers);
119
120 let request = self.client.request(Method::GET, url).query(params);
121
122 let response = send(request).await?;
123 Ok(response)
124 }
125
126 #[tracing::instrument(skip(self), err)]
131 pub async fn get_orderbook(
132 &self,
133 params: &GetOrderbookParams,
134 ) -> Result<Response<Orderbook>, Error> {
135 let url = format!("{}{}", self.base_url, Path::MarketOrderbook);
136
137 let request = self.client.request(Method::GET, url).query(params);
138
139 let response = send(request).await?;
140 Ok(response)
141 }
142
143 #[tracing::instrument(skip(self), err)]
144 pub async fn get_instruments_info(
145 &self,
146 params: &GetInstrumentsInfoParams,
147 ) -> Result<Response<InstrumentsInfo>, Error> {
148 let url = format!("{}{}", self.base_url, Path::MarketInstrumentsInfo);
149
150 let request = self.client.request(Method::GET, url).query(params);
151
152 let response = send(request).await?;
153 Ok(response)
154 }
155
156 #[tracing::instrument(skip(self), err)]
157 pub async fn get_public_recent_trading_history(
158 &self,
159 params: &GetTradesParams,
160 ) -> Result<Response<Trade>, Error> {
161 let url = format!("{}{}", self.base_url, Path::MarketRecentTrade);
162
163 let request = self.client.request(Method::GET, url).query(params);
164
165 let response = send(request).await?;
166 Ok(response)
167 }
168}
169
170impl Client {
172 #[tracing::instrument(skip(self), err)]
219 pub async fn place_order(
220 &self,
221 request: &PlaceOrderRequest,
222 ) -> Result<Response<PlaceOrderResponse>, Error> {
223 let url = format!("{}{}", self.base_url, Path::TradeOrderCreate);
224 let json = serialize_json(request)?;
225 let headers = self.get_signed_headers(&json);
226
227 let request = self
228 .client
229 .request(Method::POST, url)
230 .headers(headers)
231 .body(json);
232
233 let response = send(request).await?;
234 Ok(response)
235 }
236
237 #[tracing::instrument(skip(self), err)]
241 pub async fn amend_order(
242 &self,
243 request: &AmendOrderRequest,
244 ) -> Result<Response<AmendOrderResponse>, Error> {
245 let url = format!("{}{}", self.base_url, Path::TradeOrderAmend);
246 let json = serialize_json(request)?;
247 let headers = self.get_signed_headers(&json);
248
249 let request = self
250 .client
251 .request(Method::POST, url)
252 .headers(headers)
253 .body(json);
254
255 let response = send(request).await?;
256 Ok(response)
257 }
258
259 #[tracing::instrument(skip(self), err)]
265 pub async fn cancel_order(
266 &self,
267 request: &CancelOrderRequest,
268 ) -> Result<Response<CancelOrderResponse>, Error> {
269 let url = format!("{}{}", self.base_url, Path::TradeOrderCancel);
270 let json = serialize_json(request)?;
271 let headers = self.get_signed_headers(&json);
272
273 let request = self
274 .client
275 .request(Method::POST, url)
276 .headers(headers)
277 .body(json);
278
279 let response = send(request).await?;
280 Ok(response)
281 }
282
283 #[tracing::instrument(skip(self), err)]
297 pub async fn get_open_closed_orders(
298 &self,
299 params: &GetOpenClosedOrdersParams,
300 ) -> Result<Response<CursorPagination<Order>>, Error> {
301 let query = serialize_query(params)?;
302 let url = format!("{}{}?{query}", self.base_url, Path::TradeOrderRealtime);
303 let headers = self.get_signed_headers(&query);
304
305 let request = self.client.request(Method::GET, url).headers(headers);
306
307 let response = send(request).await?;
308 Ok(response)
309 }
310
311 #[tracing::instrument(skip(self), err)]
315 pub async fn get_open_closed_orders_all(
316 &self,
317 params: &GetOpenClosedOrdersParams,
318 ) -> Result<Vec<Order>, Error> {
319 let mut all = Vec::new();
320 let mut p = params.clone();
321 loop {
322 let page = self.get_open_closed_orders(&p).await?;
323 all.extend(page.result.list);
324 match page.result.next_page_cursor {
325 Some(cursor) => p = p.with_cursor(cursor),
326 None => break,
327 }
328 }
329 Ok(all)
330 }
331
332 #[tracing::instrument(skip(self), err)]
335 pub async fn cancel_all_orders(
336 &self,
337 request: &CancelAllOrdersRequest,
338 ) -> Result<Response<CancelAllOrdersResponse>, Error> {
339 let url = format!("{}{}", self.base_url, Path::TradeOrderCancelAll);
340 let json = serialize_json(request)?;
341 let headers = self.get_signed_headers(&json);
342
343 let request = self
344 .client
345 .request(Method::POST, url)
346 .headers(headers)
347 .body(json);
348
349 let response = send(request).await?;
350 Ok(response)
351 }
352
353 #[tracing::instrument(skip(self), err)]
357 pub async fn get_order_history(
358 &self,
359 params: &GetOrderHistoryParams,
360 ) -> Result<Response<CursorPagination<Order>>, Error> {
361 let query = serialize_query(params)?;
362 let url = format!("{}{}?{query}", self.base_url, Path::TradeOrderHistory);
363 let headers = self.get_signed_headers(&query);
364
365 let request = self.client.request(Method::GET, url).headers(headers);
366
367 let response = send(request).await?;
368 Ok(response)
369 }
370
371 #[tracing::instrument(skip(self), err)]
373 pub async fn get_order_history_all(
374 &self,
375 params: &GetOrderHistoryParams,
376 ) -> Result<Vec<Order>, Error> {
377 let mut all = Vec::new();
378 let mut p = params.clone();
379 loop {
380 let page = self.get_order_history(&p).await?;
381 all.extend(page.result.list);
382 match page.result.next_page_cursor {
383 Some(cursor) => p = p.with_cursor(cursor),
384 None => break,
385 }
386 }
387 Ok(all)
388 }
389
390 #[tracing::instrument(skip(self), err)]
394 pub async fn place_orders_batch(
395 &self,
396 request: &PlaceOrderBatchRequest,
397 ) -> Result<Response<List<PlaceOrderBatchResult>>, Error> {
398 let url = format!("{}{}", self.base_url, Path::TradeOrderCreateBatch);
399 let json = serialize_json(request)?;
400 let headers = self.get_signed_headers(&json);
401
402 let request = self
403 .client
404 .request(Method::POST, url)
405 .headers(headers)
406 .body(json);
407
408 let response = send(request).await?;
409 Ok(response)
410 }
411
412 #[tracing::instrument(skip(self), err)]
416 pub async fn amend_orders_batch(
417 &self,
418 request: &AmendOrderBatchRequest,
419 ) -> Result<Response<List<AmendOrderBatchResult>>, Error> {
420 let url = format!("{}{}", self.base_url, Path::TradeOrderAmendBatch);
421 let json = serialize_json(request)?;
422 let headers = self.get_signed_headers(&json);
423
424 let request = self
425 .client
426 .request(Method::POST, url)
427 .headers(headers)
428 .body(json);
429
430 let response = send(request).await?;
431 Ok(response)
432 }
433
434 #[tracing::instrument(skip(self), err)]
438 pub async fn cancel_orders_batch(
439 &self,
440 request: &CancelOrderBatchRequest,
441 ) -> Result<Response<List<CancelOrderBatchResult>>, Error> {
442 let url = format!("{}{}", self.base_url, Path::TradeOrderCancelBatch);
443 let json = serialize_json(request)?;
444 let headers = self.get_signed_headers(&json);
445
446 let request = self
447 .client
448 .request(Method::POST, url)
449 .headers(headers)
450 .body(json);
451
452 let response = send(request).await?;
453 Ok(response)
454 }
455}
456
457impl Client {
459 #[tracing::instrument(skip(self), err)]
470 pub async fn get_position_info(
471 &self,
472 params: &GetPositionInfoParams,
473 ) -> Result<Response<CursorPagination<Position>>, Error> {
474 let query = serialize_query(params)?;
475 let url = format!("{}{}?{query}", self.base_url, Path::PositionList);
476 let headers = self.get_signed_headers(&query);
477
478 let request = self.client.request(Method::GET, url).headers(headers);
479
480 let response = send(request).await?;
481 Ok(response)
482 }
483
484 #[tracing::instrument(skip(self), err)]
488 pub async fn get_position_info_all(
489 &self,
490 params: &GetPositionInfoParams,
491 ) -> Result<Vec<Position>, Error> {
492 let mut all = Vec::new();
493 let mut p = params.clone();
494 loop {
495 let page = self.get_position_info(&p).await?;
496 all.extend(page.result.list);
497 match page.result.next_page_cursor {
498 Some(cursor) => p = p.with_cursor(cursor),
499 None => break,
500 }
501 }
502 Ok(all)
503 }
504
505 #[tracing::instrument(skip(self), err)]
508 pub async fn set_leverage(
509 &self,
510 request: &SetLeverageRequest,
511 ) -> Result<Response<EmptyResult>, Error> {
512 let url = format!("{}{}", self.base_url, Path::PositionSetLeverage);
513 let json = serialize_json(request)?;
514 let headers = self.get_signed_headers(&json);
515
516 let request = self
517 .client
518 .request(Method::POST, url)
519 .headers(headers)
520 .body(json);
521
522 let response = send(request).await?;
523 Ok(response)
524 }
525
526 #[tracing::instrument(skip(self), err)]
529 pub async fn set_trading_stop(
530 &self,
531 request: &SetTradingStopRequest,
532 ) -> Result<Response<EmptyResult>, Error> {
533 let url = format!("{}{}", self.base_url, Path::PositionTradingStop);
534 let json = serialize_json(request)?;
535 let headers = self.get_signed_headers(&json);
536
537 let request = self
538 .client
539 .request(Method::POST, url)
540 .headers(headers)
541 .body(json);
542
543 let response = send(request).await?;
544 Ok(response)
545 }
546
547 #[tracing::instrument(skip(self), err)]
550 pub async fn switch_cross_isolated_margin(
551 &self,
552 request: &SwitchCrossIsolatedMarginRequest,
553 ) -> Result<Response<EmptyResult>, Error> {
554 let url = format!("{}{}", self.base_url, Path::PositionSwitchIsolated);
555 let json = serialize_json(request)?;
556 let headers = self.get_signed_headers(&json);
557
558 let request = self
559 .client
560 .request(Method::POST, url)
561 .headers(headers)
562 .body(json);
563
564 let response = send(request).await?;
565 Ok(response)
566 }
567
568 #[tracing::instrument(skip(self), err)]
571 pub async fn switch_position_mode(
572 &self,
573 request: &SwitchPositionModeRequest,
574 ) -> Result<Response<EmptyResult>, Error> {
575 let url = format!("{}{}", self.base_url, Path::PositionSwitchMode);
576 let json = serialize_json(request)?;
577 let headers = self.get_signed_headers(&json);
578
579 let request = self
580 .client
581 .request(Method::POST, url)
582 .headers(headers)
583 .body(json);
584
585 let response = send(request).await?;
586 Ok(response)
587 }
588
589 #[tracing::instrument(skip(self), err)]
592 pub async fn set_auto_add_margin(
593 &self,
594 request: &SetAutoAddMarginRequest,
595 ) -> Result<Response<EmptyResult>, Error> {
596 let url = format!("{}{}", self.base_url, Path::PositionSetAutoAddMargin);
597 let json = serialize_json(request)?;
598 let headers = self.get_signed_headers(&json);
599
600 let request = self
601 .client
602 .request(Method::POST, url)
603 .headers(headers)
604 .body(json);
605
606 let response = send(request).await?;
607 Ok(response)
608 }
609
610 #[tracing::instrument(skip(self), err)]
613 pub async fn set_risk_limit(
614 &self,
615 request: &SetRiskLimitRequest,
616 ) -> Result<Response<SetRiskLimitResponse>, Error> {
617 let url = format!("{}{}", self.base_url, Path::PositionSetRiskLimit);
618 let json = serialize_json(request)?;
619 let headers = self.get_signed_headers(&json);
620
621 let request = self
622 .client
623 .request(Method::POST, url)
624 .headers(headers)
625 .body(json);
626
627 let response = send(request).await?;
628 Ok(response)
629 }
630
631 #[tracing::instrument(skip(self), err)]
634 pub async fn get_closed_pnl(
635 &self,
636 params: &GetClosedPnlParams,
637 ) -> Result<Response<CursorPagination<ClosedPnl>>, Error> {
638 let query = serialize_query(params)?;
639 let url = format!("{}{}?{query}", self.base_url, Path::PositionClosedPnl);
640 let headers = self.get_signed_headers(&query);
641
642 let request = self.client.request(Method::GET, url).headers(headers);
643
644 let response = send(request).await?;
645 Ok(response)
646 }
647
648 #[tracing::instrument(skip(self), err)]
650 pub async fn get_closed_pnl_all(
651 &self,
652 params: &GetClosedPnlParams,
653 ) -> Result<Vec<ClosedPnl>, Error> {
654 let mut all = Vec::new();
655 let mut p = params.clone();
656 loop {
657 let page = self.get_closed_pnl(&p).await?;
658 all.extend(page.result.list);
659 match page.result.next_page_cursor {
660 Some(cursor) => p = p.with_cursor(cursor),
661 None => break,
662 }
663 }
664 Ok(all)
665 }
666
667 #[tracing::instrument(skip(self), err)]
670 pub async fn get_execution_list(
671 &self,
672 params: &GetExecutionListParams,
673 ) -> Result<Response<CursorPagination<ExecutionEntry>>, Error> {
674 let query = serialize_query(params)?;
675 let url = format!("{}{}?{query}", self.base_url, Path::ExecutionList);
676 let headers = self.get_signed_headers(&query);
677
678 let request = self.client.request(Method::GET, url).headers(headers);
679
680 let response = send(request).await?;
681 Ok(response)
682 }
683
684 #[tracing::instrument(skip(self), err)]
686 pub async fn get_execution_list_all(
687 &self,
688 params: &GetExecutionListParams,
689 ) -> Result<Vec<ExecutionEntry>, Error> {
690 let mut all = Vec::new();
691 let mut p = params.clone();
692 loop {
693 let page = self.get_execution_list(&p).await?;
694 all.extend(page.result.list);
695 match page.result.next_page_cursor {
696 Some(cursor) => p = p.with_cursor(cursor),
697 None => break,
698 }
699 }
700 Ok(all)
701 }
702}
703
704impl Client {
706 #[tracing::instrument(skip(self), err)]
708 pub async fn get_wallet_balance(
709 &self,
710 params: &GetWalletBalanceParams,
711 ) -> Result<Response<List<WalletBalance>>, Error> {
712 let query = serialize_query(params)?;
713 let url = format!("{}{}?{query}", self.base_url, Path::AccountWalletBalance);
714 let headers = self.get_signed_headers(&query);
715
716 let request = self.client.request(Method::GET, url).headers(headers);
717
718 let response = send(request).await?;
719 Ok(response)
720 }
721
722 #[tracing::instrument(skip(self), err)]
725 pub async fn get_transaction_log(
726 &self,
727 params: &GetTransactionLogParams,
728 ) -> Result<Response<CursorPagination<TransactionLog>>, Error> {
729 let query = serialize_query(params)?;
730 let url = format!("{}{}?{query}", self.base_url, Path::AccountTransactionLog);
731 let headers = self.get_signed_headers(&query);
732
733 let request = self.client.request(Method::GET, url).headers(headers);
734
735 let response = send(request).await?;
736 Ok(response)
737 }
738
739 #[tracing::instrument(skip(self), err)]
743 pub async fn get_transaction_log_all(
744 &self,
745 params: &GetTransactionLogParams,
746 ) -> Result<Vec<TransactionLog>, Error> {
747 let mut all = Vec::new();
748 let mut p = params.clone();
749 loop {
750 let page = self.get_transaction_log(&p).await?;
751 all.extend(page.result.list);
752 match page.result.next_page_cursor {
753 Some(cursor) => p = p.with_cursor(cursor),
754 None => break,
755 }
756 }
757 Ok(all)
758 }
759
760 #[tracing::instrument(skip(self), err)]
762 pub async fn get_account_info(&self) -> Result<Response<AccountInfo>, Error> {
763 let url = format!("{}{}", self.base_url, Path::AccountInfo);
764 let query = "";
765 let headers = self.get_signed_headers(query);
766
767 let request = self.client.request(Method::GET, url).headers(headers);
768
769 let response = send(request).await?;
770 Ok(response)
771 }
772}
773
774impl Client {
776 #[tracing::instrument(skip(self), err)]
779 pub async fn get_api_key_information(&self) -> Result<Response<APIKeyInformation>, Error> {
780 let url = format!("{}{}", self.base_url, Path::UserQueryApi);
781 let query = "";
782 let headers = self.get_signed_headers(query);
783
784 let request = self.client.request(Method::GET, url).headers(headers);
785
786 let response = send(request).await?;
787 Ok(response)
788 }
789}
790
791async fn send<T>(request: RequestBuilder) -> Result<Response<T>, Error>
792where
793 T: serde::de::DeserializeOwned,
794{
795 let start = std::time::Instant::now();
796 let response = request.send().await?;
797 let elapsed_ms = start.elapsed().as_millis();
798 let headers = parse_headers(response.headers());
799 let json = response.text().await?;
800 if !headers.is_ret_code_ok() {
801 let msg: APIErrorResponse = deserialize_json(&json)?;
802 tracing::debug!(elapsed_ms, ret_code = msg.ret_code, "api error response");
803 return Err(msg.into());
804 }
805
806 tracing::debug!(
807 elapsed_ms,
808 api_limit = headers.api_limit,
809 api_limit_status = headers.api_limit_status,
810 "api call completed"
811 );
812 let response: Resp<_> = deserialize_json(&json)?;
813 let response = Response {
814 result: response.result,
815 time: response.time,
816 headers,
817 ret_ext_info: response.ret_ext_info,
818 };
819 Ok(response)
820}
821
822fn parse_headers(headers: &HeaderMap) -> Headers {
824 let ret_code = headers
825 .get(HEADER_RET_CODE)
826 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
827 let trace_id = headers
828 .get(HEADER_TRACE_ID)
829 .and_then(|h| h.to_str().map(|str| str.into()).ok());
830 let time_now = headers
831 .get(HEADER_TIME_NOW)
832 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
833
834 let api_limit = headers
835 .get(HEADER_X_BAPI_LIMIT)
836 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
837 let api_limit_status = headers
838 .get(HEADER_X_BAPI_LIMIT_STATUS)
839 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
840 let api_limit_reset_timestamp = headers
841 .get(HEADER_X_BAPI_LIMIT_RESET_TIMESTAMP)
842 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
843
844 Headers {
845 ret_code,
846 trace_id,
847 time_now,
848 api_limit,
849 api_limit_status,
850 api_limit_reset_timestamp,
851 }
852}