1use serde::{Deserialize, Serialize};
4
5use crate::client::OkxClient;
6use crate::error::Error;
7use crate::model::{InstType, NumberString, PositionSide, TradeMode};
8use crate::transport::Transport;
9
10const BALANCE: &str = "/api/v5/account/balance";
11const POSITIONS: &str = "/api/v5/account/positions";
12const POSITION_RISK: &str = "/api/v5/account/account-position-risk";
13const ACCOUNT_CONFIG: &str = "/api/v5/account/config";
14const BILLS: &str = "/api/v5/account/bills";
15const BILLS_ARCHIVE: &str = "/api/v5/account/bills-archive";
16const SET_POSITION_MODE: &str = "/api/v5/account/set-position-mode";
17const SET_LEVERAGE: &str = "/api/v5/account/set-leverage";
18const GET_LEVERAGE: &str = "/api/v5/account/leverage-info";
19const MAX_ORDER_SIZE: &str = "/api/v5/account/max-size";
20const MAX_AVAILABLE_SIZE: &str = "/api/v5/account/max-avail-size";
21const ADJUST_MARGIN: &str = "/api/v5/account/position/margin-balance";
22const FEE_RATES: &str = "/api/v5/account/trade-fee";
23const ACCOUNT_INSTRUMENTS: &str = "/api/v5/account/instruments";
24const MAX_LOAN: &str = "/api/v5/account/max-loan";
25const INTEREST_ACCRUED: &str = "/api/v5/account/interest-accrued";
26const INTEREST_RATE: &str = "/api/v5/account/interest-rate";
27const SET_GREEKS: &str = "/api/v5/account/set-greeks";
28const SET_ISOLATED_MODE: &str = "/api/v5/account/set-isolated-mode";
29const MAX_WITHDRAWAL: &str = "/api/v5/account/max-withdrawal";
30const BORROW_REPAY: &str = "/api/v5/account/borrow-repay";
31const BORROW_REPAY_HISTORY: &str = "/api/v5/account/borrow-repay-history";
32const INTEREST_LIMITS: &str = "/api/v5/account/interest-limits";
33const SIMULATED_MARGIN: &str = "/api/v5/account/simulated_margin";
34const GREEKS: &str = "/api/v5/account/greeks";
35const POSITIONS_HISTORY: &str = "/api/v5/account/positions-history";
36const ACCOUNT_POSITION_TIERS: &str = "/api/v5/account/position-tiers";
37const RISK_STATE: &str = "/api/v5/account/risk-state";
38const SET_RISK_OFFSET_TYPE: &str = "/api/v5/account/set-riskOffset-type";
39const SET_AUTO_LOAN: &str = "/api/v5/account/set-auto-loan";
40const SET_ACCOUNT_LEVEL: &str = "/api/v5/account/set-account-level";
41const ACTIVATE_OPTION: &str = "/api/v5/account/activate-option";
42const POSITION_BUILDER: &str = "/api/v5/account/position-builder";
43
44pub struct Account<'a, T> {
50 client: &'a OkxClient<T>,
51}
52
53impl<'a, T: Transport> Account<'a, T> {
54 pub(crate) fn new(client: &'a OkxClient<T>) -> Self {
55 Self { client }
56 }
57
58 pub async fn get_balance(&self, ccy: Option<&str>) -> Result<Vec<AccountBalance>, Error> {
69 let query = BalanceQuery { ccy };
70 self.client.get(BALANCE, &query, true).await
71 }
72
73 pub async fn get_positions(
82 &self,
83 inst_type: Option<InstType>,
84 inst_id: Option<&str>,
85 ) -> Result<Vec<Position>, Error> {
86 let query = PositionsQuery {
87 inst_type: inst_type.as_ref(),
88 inst_id,
89 };
90 self.client.get(POSITIONS, &query, true).await
91 }
92
93 pub async fn get_position_risk(
101 &self,
102 inst_type: Option<InstType>,
103 ) -> Result<Vec<PositionRisk>, Error> {
104 let query = PositionRiskQuery {
105 inst_type: inst_type.as_ref(),
106 };
107 self.client.get(POSITION_RISK, &query, true).await
108 }
109
110 pub async fn get_account_config(&self) -> Result<Vec<AccountConfig>, Error> {
118 self.client.get(ACCOUNT_CONFIG, &NoQuery, true).await
119 }
120
121 pub async fn get_account_bills(&self, request: &BillsRequest) -> Result<Vec<AccountBill>, Error> {
129 self.client.get(BILLS, request, true).await
130 }
131
132 pub async fn get_account_bills_archive(
140 &self,
141 request: &BillsArchiveRequest,
142 ) -> Result<Vec<AccountBill>, Error> {
143 self.client.get(BILLS_ARCHIVE, request, true).await
144 }
145
146 pub async fn set_position_mode(
154 &self,
155 pos_mode: &str,
156 ) -> Result<Vec<SetPositionModeResult>, Error> {
157 let body = SetPositionModeBody { pos_mode };
158 self.client.post(SET_POSITION_MODE, &body, true).await
159 }
160
161 pub async fn set_leverage(
169 &self,
170 request: &SetLeverageRequest,
171 ) -> Result<Vec<LeverageInfo>, Error> {
172 self.client.post(SET_LEVERAGE, request, true).await
173 }
174
175 pub async fn get_leverage(
183 &self,
184 request: &LeverageRequest,
185 ) -> Result<Vec<LeverageInfo>, Error> {
186 self.client.get(GET_LEVERAGE, request, true).await
187 }
188
189 pub async fn get_max_order_size(
197 &self,
198 request: &MaxOrderSizeRequest,
199 ) -> Result<Vec<MaxOrderSize>, Error> {
200 self.client.get(MAX_ORDER_SIZE, request, true).await
201 }
202
203 pub async fn get_max_avail_size(
211 &self,
212 request: &MaxAvailableSizeRequest,
213 ) -> Result<Vec<MaxAvailableSize>, Error> {
214 self.client.get(MAX_AVAILABLE_SIZE, request, true).await
215 }
216
217 pub async fn adjust_margin(
225 &self,
226 request: &AdjustMarginRequest,
227 ) -> Result<Vec<AdjustMarginResult>, Error> {
228 self.client.post(ADJUST_MARGIN, request, true).await
229 }
230
231 pub async fn get_fee_rates(
239 &self,
240 request: &FeeRatesRequest,
241 ) -> Result<Vec<FeeRate>, Error> {
242 self.client.get(FEE_RATES, request, true).await
243 }
244
245 pub async fn get_account_instruments(
253 &self,
254 request: &AccountInstrumentsRequest,
255 ) -> Result<Vec<AccountInstrument>, Error> {
256 self.client.get(ACCOUNT_INSTRUMENTS, request, true).await
257 }
258
259 pub async fn get_max_loan(&self, request: &MaxLoanRequest) -> Result<Vec<MaxLoan>, Error> {
267 self.client.get(MAX_LOAN, request, true).await
268 }
269
270 pub async fn get_interest_accrued(
278 &self,
279 request: &InterestAccruedRequest,
280 ) -> Result<Vec<InterestAccrued>, Error> {
281 self.client.get(INTEREST_ACCRUED, request, true).await
282 }
283
284 pub async fn get_interest_rate(
292 &self,
293 ccy: Option<&str>,
294 ) -> Result<Vec<InterestRate>, Error> {
295 let query = BalanceQuery { ccy };
296 self.client.get(INTEREST_RATE, &query, true).await
297 }
298
299 pub async fn set_greeks(&self, greeks_type: &str) -> Result<Vec<SetGreeksResult>, Error> {
307 let body = SetGreeksBody { greeks_type };
308 self.client.post(SET_GREEKS, &body, true).await
309 }
310
311 pub async fn set_isolated_mode(
319 &self,
320 iso_mode: &str,
321 mode_type: &str,
322 ) -> Result<Vec<SetIsolatedModeResult>, Error> {
323 let body = SetIsolatedModeBody {
324 iso_mode,
325 mode_type,
326 };
327 self.client.post(SET_ISOLATED_MODE, &body, true).await
328 }
329
330 pub async fn get_max_withdrawal(
338 &self,
339 ccy: Option<&str>,
340 ) -> Result<Vec<MaxWithdrawal>, Error> {
341 let query = BalanceQuery { ccy };
342 self.client.get(MAX_WITHDRAWAL, &query, true).await
343 }
344
345 pub async fn borrow_repay(
353 &self,
354 request: &BorrowRepayRequest,
355 ) -> Result<Vec<BorrowRepayResult>, Error> {
356 self.client.post(BORROW_REPAY, request, true).await
357 }
358
359 pub async fn get_borrow_repay_history(
367 &self,
368 request: &BorrowRepayHistoryRequest,
369 ) -> Result<Vec<BorrowRepayHistory>, Error> {
370 self.client.get(BORROW_REPAY_HISTORY, request, true).await
371 }
372
373 pub async fn get_interest_limits(
381 &self,
382 request: &InterestLimitsRequest,
383 ) -> Result<Vec<InterestLimit>, Error> {
384 self.client.get(INTEREST_LIMITS, request, true).await
385 }
386
387 pub async fn get_simulated_margin(
397 &self,
398 request: &SimulatedMarginRequest,
399 ) -> Result<Vec<SimulatedMargin>, Error> {
400 self.client.post(SIMULATED_MARGIN, request, true).await
401 }
402
403 pub async fn get_greeks(&self, ccy: Option<&str>) -> Result<Vec<Greek>, Error> {
411 let query = BalanceQuery { ccy };
412 self.client.get(GREEKS, &query, true).await
413 }
414
415 pub async fn get_positions_history(
423 &self,
424 request: &PositionsHistoryRequest,
425 ) -> Result<Vec<PositionHistory>, Error> {
426 self.client.get(POSITIONS_HISTORY, request, true).await
427 }
428
429 pub async fn get_account_position_tiers(
437 &self,
438 request: &AccountPositionTiersRequest,
439 ) -> Result<Vec<AccountPositionTier>, Error> {
440 self.client.get(ACCOUNT_POSITION_TIERS, request, true).await
441 }
442
443 pub async fn get_risk_state(&self) -> Result<Vec<RiskState>, Error> {
451 self.client.get(RISK_STATE, &NoQuery, true).await
452 }
453
454 pub async fn set_risk_offset_type(
462 &self,
463 risk_offset_type: &str,
464 ) -> Result<Vec<SetRiskOffsetTypeResult>, Error> {
465 let body = TypeBody {
466 value: risk_offset_type,
467 };
468 self.client.post(SET_RISK_OFFSET_TYPE, &body, true).await
469 }
470
471 pub async fn set_auto_loan(&self, auto_loan: bool) -> Result<Vec<SetAutoLoanResult>, Error> {
479 let body = SetAutoLoanBody { auto_loan };
480 self.client.post(SET_AUTO_LOAN, &body, true).await
481 }
482
483 pub async fn set_account_level(
491 &self,
492 acct_lv: &str,
493 ) -> Result<Vec<SetAccountLevelResult>, Error> {
494 let body = SetAccountLevelBody { acct_lv };
495 self.client.post(SET_ACCOUNT_LEVEL, &body, true).await
496 }
497
498 pub async fn activate_option(&self) -> Result<Vec<ActivateOptionResult>, Error> {
506 self.client.post(ACTIVATE_OPTION, &EmptyBody {}, true).await
507 }
508
509 pub async fn position_builder(
517 &self,
518 request: &PositionBuilderRequest,
519 ) -> Result<Vec<PositionBuilderResult>, Error> {
520 self.client.post(POSITION_BUILDER, request, true).await
521 }
522}
523
524#[derive(Serialize)]
525struct NoQuery;
526
527#[derive(Serialize)]
528struct EmptyBody {}
529
530#[derive(Serialize)]
531struct BalanceQuery<'a> {
532 #[serde(skip_serializing_if = "Option::is_none")]
533 ccy: Option<&'a str>,
534}
535
536#[derive(Serialize)]
537struct PositionsQuery<'a> {
538 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
539 inst_type: Option<&'a InstType>,
540 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
541 inst_id: Option<&'a str>,
542}
543
544#[derive(Serialize)]
545struct PositionRiskQuery<'a> {
546 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
547 inst_type: Option<&'a InstType>,
548}
549
550#[derive(Serialize)]
551struct SetPositionModeBody<'a> {
552 #[serde(rename = "posMode")]
553 pos_mode: &'a str,
554}
555
556#[derive(Debug, Clone, Default, Serialize)]
558pub struct BillsRequest {
559 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
560 inst_type: Option<InstType>,
561 #[serde(skip_serializing_if = "Option::is_none")]
562 ccy: Option<String>,
563 #[serde(rename = "mgnMode", skip_serializing_if = "Option::is_none")]
564 mgn_mode: Option<TradeMode>,
565 #[serde(rename = "ctType", skip_serializing_if = "Option::is_none")]
566 ct_type: Option<String>,
567 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
568 bill_type: Option<String>,
569 #[serde(rename = "subType", skip_serializing_if = "Option::is_none")]
570 sub_type: Option<String>,
571 #[serde(skip_serializing_if = "Option::is_none")]
572 after: Option<String>,
573 #[serde(skip_serializing_if = "Option::is_none")]
574 before: Option<String>,
575 #[serde(skip_serializing_if = "Option::is_none")]
576 limit: Option<u32>,
577}
578
579impl BillsRequest {
580 pub fn new() -> Self {
582 Self::default()
583 }
584
585 pub fn inst_type(mut self, inst_type: InstType) -> Self {
587 self.inst_type = Some(inst_type);
588 self
589 }
590
591 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
593 self.ccy = Some(ccy.into());
594 self
595 }
596
597 pub fn margin_mode(mut self, mgn_mode: TradeMode) -> Self {
599 self.mgn_mode = Some(mgn_mode);
600 self
601 }
602
603 pub fn contract_type(mut self, ct_type: impl Into<String>) -> Self {
605 self.ct_type = Some(ct_type.into());
606 self
607 }
608
609 pub fn bill_type(mut self, bill_type: impl Into<String>) -> Self {
611 self.bill_type = Some(bill_type.into());
612 self
613 }
614
615 pub fn sub_type(mut self, sub_type: impl Into<String>) -> Self {
617 self.sub_type = Some(sub_type.into());
618 self
619 }
620
621 pub fn after(mut self, after: impl Into<String>) -> Self {
623 self.after = Some(after.into());
624 self
625 }
626
627 pub fn before(mut self, before: impl Into<String>) -> Self {
629 self.before = Some(before.into());
630 self
631 }
632
633 pub fn limit(mut self, limit: u32) -> Self {
635 self.limit = Some(limit);
636 self
637 }
638}
639
640#[derive(Debug, Clone, Default, Serialize)]
642pub struct BillsArchiveRequest {
643 #[serde(flatten)]
644 base: BillsRequest,
645 #[serde(skip_serializing_if = "Option::is_none")]
646 begin: Option<String>,
647 #[serde(skip_serializing_if = "Option::is_none")]
648 end: Option<String>,
649}
650
651impl BillsArchiveRequest {
652 pub fn new() -> Self {
654 Self::default()
655 }
656
657 pub fn filters(mut self, base: BillsRequest) -> Self {
659 self.base = base;
660 self
661 }
662
663 pub fn begin(mut self, begin: impl Into<String>) -> Self {
665 self.begin = Some(begin.into());
666 self
667 }
668
669 pub fn end(mut self, end: impl Into<String>) -> Self {
671 self.end = Some(end.into());
672 self
673 }
674}
675
676#[derive(Debug, Clone, Serialize)]
678pub struct SetLeverageRequest {
679 lever: String,
680 #[serde(rename = "mgnMode")]
681 mgn_mode: TradeMode,
682 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
683 inst_id: Option<String>,
684 #[serde(skip_serializing_if = "Option::is_none")]
685 ccy: Option<String>,
686 #[serde(rename = "posSide", skip_serializing_if = "Option::is_none")]
687 pos_side: Option<PositionSide>,
688}
689
690impl SetLeverageRequest {
691 pub fn new(lever: impl Into<String>, mgn_mode: TradeMode) -> Self {
693 Self {
694 lever: lever.into(),
695 mgn_mode,
696 inst_id: None,
697 ccy: None,
698 pos_side: None,
699 }
700 }
701
702 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
704 self.inst_id = Some(inst_id.into());
705 self
706 }
707
708 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
710 self.ccy = Some(ccy.into());
711 self
712 }
713
714 pub fn position_side(mut self, pos_side: PositionSide) -> Self {
716 self.pos_side = Some(pos_side);
717 self
718 }
719}
720
721#[derive(Debug, Clone, Serialize)]
723pub struct LeverageRequest {
724 #[serde(rename = "mgnMode")]
725 mgn_mode: TradeMode,
726 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
727 inst_id: Option<String>,
728 #[serde(skip_serializing_if = "Option::is_none")]
729 ccy: Option<String>,
730}
731
732impl LeverageRequest {
733 pub fn new(mgn_mode: TradeMode) -> Self {
735 Self {
736 mgn_mode,
737 inst_id: None,
738 ccy: None,
739 }
740 }
741
742 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
744 self.inst_id = Some(inst_id.into());
745 self
746 }
747
748 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
750 self.ccy = Some(ccy.into());
751 self
752 }
753}
754
755#[derive(Debug, Clone, Serialize)]
757pub struct MaxOrderSizeRequest {
758 #[serde(rename = "instId")]
759 inst_id: String,
760 #[serde(rename = "tdMode")]
761 td_mode: TradeMode,
762 #[serde(skip_serializing_if = "Option::is_none")]
763 ccy: Option<String>,
764 #[serde(skip_serializing_if = "Option::is_none")]
765 px: Option<String>,
766 #[serde(rename = "tradeQuoteCcy", skip_serializing_if = "Option::is_none")]
767 trade_quote_ccy: Option<String>,
768}
769
770impl MaxOrderSizeRequest {
771 pub fn new(inst_id: impl Into<String>, td_mode: TradeMode) -> Self {
773 Self {
774 inst_id: inst_id.into(),
775 td_mode,
776 ccy: None,
777 px: None,
778 trade_quote_ccy: None,
779 }
780 }
781
782 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
784 self.ccy = Some(ccy.into());
785 self
786 }
787
788 pub fn price(mut self, px: impl Into<String>) -> Self {
790 self.px = Some(px.into());
791 self
792 }
793
794 pub fn trade_quote_currency(mut self, trade_quote_ccy: impl Into<String>) -> Self {
796 self.trade_quote_ccy = Some(trade_quote_ccy.into());
797 self
798 }
799}
800
801#[derive(Debug, Clone, Serialize)]
803pub struct MaxAvailableSizeRequest {
804 #[serde(rename = "instId")]
805 inst_id: String,
806 #[serde(rename = "tdMode")]
807 td_mode: TradeMode,
808 #[serde(skip_serializing_if = "Option::is_none")]
809 ccy: Option<String>,
810 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
811 reduce_only: Option<bool>,
812 #[serde(rename = "unSpotOffset", skip_serializing_if = "Option::is_none")]
813 un_spot_offset: Option<bool>,
814 #[serde(rename = "quickMgnType", skip_serializing_if = "Option::is_none")]
815 quick_mgn_type: Option<String>,
816 #[serde(rename = "tradeQuoteCcy", skip_serializing_if = "Option::is_none")]
817 trade_quote_ccy: Option<String>,
818}
819
820impl MaxAvailableSizeRequest {
821 pub fn new(inst_id: impl Into<String>, td_mode: TradeMode) -> Self {
823 Self {
824 inst_id: inst_id.into(),
825 td_mode,
826 ccy: None,
827 reduce_only: None,
828 un_spot_offset: None,
829 quick_mgn_type: None,
830 trade_quote_ccy: None,
831 }
832 }
833
834 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
836 self.ccy = Some(ccy.into());
837 self
838 }
839
840 pub fn reduce_only(mut self, reduce_only: bool) -> Self {
842 self.reduce_only = Some(reduce_only);
843 self
844 }
845
846 pub fn un_spot_offset(mut self, un_spot_offset: bool) -> Self {
848 self.un_spot_offset = Some(un_spot_offset);
849 self
850 }
851
852 pub fn quick_margin_type(mut self, quick_mgn_type: impl Into<String>) -> Self {
854 self.quick_mgn_type = Some(quick_mgn_type.into());
855 self
856 }
857
858 pub fn trade_quote_currency(mut self, trade_quote_ccy: impl Into<String>) -> Self {
860 self.trade_quote_ccy = Some(trade_quote_ccy.into());
861 self
862 }
863}
864
865#[derive(Debug, Clone, Serialize)]
867pub struct AdjustMarginRequest {
868 #[serde(rename = "instId")]
869 inst_id: String,
870 #[serde(rename = "posSide")]
871 pos_side: PositionSide,
872 #[serde(rename = "type")]
873 action: String,
874 amt: String,
875 #[serde(rename = "loanTrans", skip_serializing_if = "Option::is_none")]
876 loan_trans: Option<bool>,
877}
878
879impl AdjustMarginRequest {
880 pub fn new(
882 inst_id: impl Into<String>,
883 pos_side: PositionSide,
884 action: impl Into<String>,
885 amt: impl Into<String>,
886 ) -> Self {
887 Self {
888 inst_id: inst_id.into(),
889 pos_side,
890 action: action.into(),
891 amt: amt.into(),
892 loan_trans: None,
893 }
894 }
895
896 pub fn loan_transfer(mut self, loan_trans: bool) -> Self {
898 self.loan_trans = Some(loan_trans);
899 self
900 }
901}
902
903#[derive(Debug, Clone, Serialize)]
905pub struct FeeRatesRequest {
906 #[serde(rename = "instType")]
907 inst_type: InstType,
908 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
909 inst_id: Option<String>,
910 #[serde(rename = "uly", skip_serializing_if = "Option::is_none")]
911 underlying: Option<String>,
912 #[serde(skip_serializing_if = "Option::is_none")]
913 category: Option<String>,
914 #[serde(rename = "instFamily", skip_serializing_if = "Option::is_none")]
915 inst_family: Option<String>,
916}
917
918impl FeeRatesRequest {
919 pub fn new(inst_type: InstType) -> Self {
921 Self {
922 inst_type,
923 inst_id: None,
924 underlying: None,
925 category: None,
926 inst_family: None,
927 }
928 }
929
930 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
932 self.inst_id = Some(inst_id.into());
933 self
934 }
935
936 pub fn underlying(mut self, underlying: impl Into<String>) -> Self {
938 self.underlying = Some(underlying.into());
939 self
940 }
941
942 pub fn category(mut self, category: impl Into<String>) -> Self {
944 self.category = Some(category.into());
945 self
946 }
947
948 pub fn inst_family(mut self, inst_family: impl Into<String>) -> Self {
950 self.inst_family = Some(inst_family.into());
951 self
952 }
953}
954
955#[derive(Debug, Clone, Default, Serialize)]
957pub struct AccountInstrumentsRequest {
958 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
959 inst_type: Option<InstType>,
960 #[serde(rename = "uly", skip_serializing_if = "Option::is_none")]
961 underlying: Option<String>,
962 #[serde(rename = "instFamily", skip_serializing_if = "Option::is_none")]
963 inst_family: Option<String>,
964 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
965 inst_id: Option<String>,
966}
967
968impl AccountInstrumentsRequest {
969 pub fn new() -> Self {
971 Self::default()
972 }
973
974 pub fn inst_type(mut self, inst_type: InstType) -> Self {
976 self.inst_type = Some(inst_type);
977 self
978 }
979
980 pub fn underlying(mut self, underlying: impl Into<String>) -> Self {
982 self.underlying = Some(underlying.into());
983 self
984 }
985
986 pub fn inst_family(mut self, inst_family: impl Into<String>) -> Self {
988 self.inst_family = Some(inst_family.into());
989 self
990 }
991
992 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
994 self.inst_id = Some(inst_id.into());
995 self
996 }
997}
998
999#[derive(Debug, Clone, Serialize)]
1001pub struct MaxLoanRequest {
1002 #[serde(rename = "instId")]
1003 inst_id: String,
1004 #[serde(rename = "mgnMode")]
1005 mgn_mode: TradeMode,
1006 #[serde(rename = "mgnCcy", skip_serializing_if = "Option::is_none")]
1007 mgn_ccy: Option<String>,
1008 #[serde(rename = "tradeQuoteCcy", skip_serializing_if = "Option::is_none")]
1009 trade_quote_ccy: Option<String>,
1010}
1011
1012impl MaxLoanRequest {
1013 pub fn new(inst_id: impl Into<String>, mgn_mode: TradeMode) -> Self {
1015 Self {
1016 inst_id: inst_id.into(),
1017 mgn_mode,
1018 mgn_ccy: None,
1019 trade_quote_ccy: None,
1020 }
1021 }
1022
1023 pub fn margin_currency(mut self, mgn_ccy: impl Into<String>) -> Self {
1025 self.mgn_ccy = Some(mgn_ccy.into());
1026 self
1027 }
1028
1029 pub fn trade_quote_currency(mut self, trade_quote_ccy: impl Into<String>) -> Self {
1031 self.trade_quote_ccy = Some(trade_quote_ccy.into());
1032 self
1033 }
1034}
1035
1036#[derive(Debug, Clone, Default, Serialize)]
1038pub struct InterestAccruedRequest {
1039 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
1040 inst_id: Option<String>,
1041 #[serde(skip_serializing_if = "Option::is_none")]
1042 ccy: Option<String>,
1043 #[serde(rename = "mgnMode", skip_serializing_if = "Option::is_none")]
1044 mgn_mode: Option<TradeMode>,
1045 #[serde(skip_serializing_if = "Option::is_none")]
1046 after: Option<String>,
1047 #[serde(skip_serializing_if = "Option::is_none")]
1048 before: Option<String>,
1049 #[serde(skip_serializing_if = "Option::is_none")]
1050 limit: Option<u32>,
1051}
1052
1053impl InterestAccruedRequest {
1054 pub fn new() -> Self {
1056 Self::default()
1057 }
1058
1059 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
1061 self.inst_id = Some(inst_id.into());
1062 self
1063 }
1064
1065 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
1067 self.ccy = Some(ccy.into());
1068 self
1069 }
1070
1071 pub fn margin_mode(mut self, mgn_mode: TradeMode) -> Self {
1073 self.mgn_mode = Some(mgn_mode);
1074 self
1075 }
1076
1077 pub fn after(mut self, after: impl Into<String>) -> Self {
1079 self.after = Some(after.into());
1080 self
1081 }
1082
1083 pub fn before(mut self, before: impl Into<String>) -> Self {
1085 self.before = Some(before.into());
1086 self
1087 }
1088
1089 pub fn limit(mut self, limit: u32) -> Self {
1091 self.limit = Some(limit);
1092 self
1093 }
1094}
1095
1096#[derive(Serialize)]
1097struct SetGreeksBody<'a> {
1098 #[serde(rename = "greeksType")]
1099 greeks_type: &'a str,
1100}
1101
1102#[derive(Serialize)]
1103struct SetIsolatedModeBody<'a> {
1104 #[serde(rename = "isoMode")]
1105 iso_mode: &'a str,
1106 #[serde(rename = "type")]
1107 mode_type: &'a str,
1108}
1109
1110#[derive(Debug, Clone, Serialize)]
1112pub struct BorrowRepayRequest {
1113 ccy: String,
1114 side: String,
1115 amt: String,
1116 #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
1117 ord_id: Option<String>,
1118}
1119
1120impl BorrowRepayRequest {
1121 pub fn new(ccy: impl Into<String>, side: impl Into<String>, amt: impl Into<String>) -> Self {
1123 Self {
1124 ccy: ccy.into(),
1125 side: side.into(),
1126 amt: amt.into(),
1127 ord_id: None,
1128 }
1129 }
1130
1131 pub fn order_id(mut self, ord_id: impl Into<String>) -> Self {
1133 self.ord_id = Some(ord_id.into());
1134 self
1135 }
1136}
1137
1138#[derive(Debug, Clone, Default, Serialize)]
1140pub struct BorrowRepayHistoryRequest {
1141 #[serde(skip_serializing_if = "Option::is_none")]
1142 ccy: Option<String>,
1143 #[serde(skip_serializing_if = "Option::is_none")]
1144 after: Option<String>,
1145 #[serde(skip_serializing_if = "Option::is_none")]
1146 before: Option<String>,
1147 #[serde(skip_serializing_if = "Option::is_none")]
1148 limit: Option<u32>,
1149}
1150
1151impl BorrowRepayHistoryRequest {
1152 pub fn new() -> Self {
1154 Self::default()
1155 }
1156
1157 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
1159 self.ccy = Some(ccy.into());
1160 self
1161 }
1162
1163 pub fn after(mut self, after: impl Into<String>) -> Self {
1165 self.after = Some(after.into());
1166 self
1167 }
1168
1169 pub fn before(mut self, before: impl Into<String>) -> Self {
1171 self.before = Some(before.into());
1172 self
1173 }
1174
1175 pub fn limit(mut self, limit: u32) -> Self {
1177 self.limit = Some(limit);
1178 self
1179 }
1180}
1181
1182#[derive(Debug, Clone, Default, Serialize)]
1184pub struct InterestLimitsRequest {
1185 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
1186 limit_type: Option<String>,
1187 #[serde(skip_serializing_if = "Option::is_none")]
1188 ccy: Option<String>,
1189}
1190
1191impl InterestLimitsRequest {
1192 pub fn new() -> Self {
1194 Self::default()
1195 }
1196
1197 pub fn limit_type(mut self, limit_type: impl Into<String>) -> Self {
1199 self.limit_type = Some(limit_type.into());
1200 self
1201 }
1202
1203 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
1205 self.ccy = Some(ccy.into());
1206 self
1207 }
1208}
1209
1210#[derive(Debug, Clone, Serialize)]
1212pub struct SimulatedPosition {
1213 #[serde(rename = "instId")]
1214 inst_id: String,
1215 #[serde(skip_serializing_if = "Option::is_none")]
1216 pos: Option<String>,
1217 #[serde(rename = "avgPx", skip_serializing_if = "Option::is_none")]
1218 avg_px: Option<String>,
1219 #[serde(skip_serializing_if = "Option::is_none")]
1220 lever: Option<String>,
1221}
1222
1223impl SimulatedPosition {
1224 pub fn new(inst_id: impl Into<String>) -> Self {
1226 Self {
1227 inst_id: inst_id.into(),
1228 pos: None,
1229 avg_px: None,
1230 lever: None,
1231 }
1232 }
1233
1234 pub fn position(mut self, pos: impl Into<String>) -> Self {
1236 self.pos = Some(pos.into());
1237 self
1238 }
1239
1240 pub fn average_price(mut self, avg_px: impl Into<String>) -> Self {
1242 self.avg_px = Some(avg_px.into());
1243 self
1244 }
1245
1246 pub fn leverage(mut self, lever: impl Into<String>) -> Self {
1248 self.lever = Some(lever.into());
1249 self
1250 }
1251}
1252
1253#[derive(Debug, Clone, Serialize)]
1255pub struct SimulatedAsset {
1256 ccy: String,
1257 #[serde(skip_serializing_if = "Option::is_none")]
1258 eq: Option<String>,
1259}
1260
1261impl SimulatedAsset {
1262 pub fn new(ccy: impl Into<String>) -> Self {
1264 Self {
1265 ccy: ccy.into(),
1266 eq: None,
1267 }
1268 }
1269
1270 pub fn equity(mut self, eq: impl Into<String>) -> Self {
1272 self.eq = Some(eq.into());
1273 self
1274 }
1275}
1276
1277#[derive(Debug, Clone, Default, Serialize)]
1279pub struct SimulatedMarginRequest {
1280 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
1281 inst_type: Option<InstType>,
1282 #[serde(rename = "inclRealPos", skip_serializing_if = "Option::is_none")]
1283 include_real_positions: Option<bool>,
1284 #[serde(rename = "spotOffsetType", skip_serializing_if = "Option::is_none")]
1285 spot_offset_type: Option<String>,
1286 #[serde(rename = "simPos", skip_serializing_if = "Option::is_none")]
1287 simulated_positions: Option<Vec<SimulatedPosition>>,
1288}
1289
1290impl SimulatedMarginRequest {
1291 pub fn new() -> Self {
1293 Self::default()
1294 }
1295
1296 pub fn inst_type(mut self, inst_type: InstType) -> Self {
1298 self.inst_type = Some(inst_type);
1299 self
1300 }
1301
1302 pub fn include_real_positions(mut self, include_real_positions: bool) -> Self {
1304 self.include_real_positions = Some(include_real_positions);
1305 self
1306 }
1307
1308 pub fn spot_offset_type(mut self, spot_offset_type: impl Into<String>) -> Self {
1310 self.spot_offset_type = Some(spot_offset_type.into());
1311 self
1312 }
1313
1314 pub fn simulated_positions(mut self, simulated_positions: Vec<SimulatedPosition>) -> Self {
1316 self.simulated_positions = Some(simulated_positions);
1317 self
1318 }
1319}
1320
1321#[derive(Debug, Clone, Default, Serialize)]
1323pub struct AccountPositionTiersRequest {
1324 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
1325 inst_type: Option<InstType>,
1326 #[serde(rename = "uly", skip_serializing_if = "Option::is_none")]
1327 underlying: Option<String>,
1328 #[serde(rename = "instFamily", skip_serializing_if = "Option::is_none")]
1329 inst_family: Option<String>,
1330}
1331
1332impl AccountPositionTiersRequest {
1333 pub fn new() -> Self {
1335 Self::default()
1336 }
1337
1338 pub fn inst_type(mut self, inst_type: InstType) -> Self {
1340 self.inst_type = Some(inst_type);
1341 self
1342 }
1343
1344 pub fn underlying(mut self, underlying: impl Into<String>) -> Self {
1346 self.underlying = Some(underlying.into());
1347 self
1348 }
1349
1350 pub fn inst_family(mut self, inst_family: impl Into<String>) -> Self {
1352 self.inst_family = Some(inst_family.into());
1353 self
1354 }
1355}
1356
1357#[derive(Serialize)]
1358struct TypeBody<'a> {
1359 #[serde(rename = "type")]
1360 value: &'a str,
1361}
1362
1363#[derive(Serialize)]
1364struct SetAutoLoanBody {
1365 #[serde(rename = "autoLoan")]
1366 auto_loan: bool,
1367}
1368
1369#[derive(Serialize)]
1370struct SetAccountLevelBody<'a> {
1371 #[serde(rename = "acctLv")]
1372 acct_lv: &'a str,
1373}
1374
1375#[derive(Debug, Clone, Default, Serialize)]
1377pub struct PositionBuilderRequest {
1378 #[serde(rename = "acctLv", skip_serializing_if = "Option::is_none")]
1379 acct_lv: Option<String>,
1380 #[serde(rename = "inclRealPosAndEq", skip_serializing_if = "Option::is_none")]
1381 include_real_positions_and_equity: Option<bool>,
1382 #[serde(skip_serializing_if = "Option::is_none")]
1383 lever: Option<String>,
1384 #[serde(rename = "greeksType", skip_serializing_if = "Option::is_none")]
1385 greeks_type: Option<String>,
1386 #[serde(rename = "simPos", skip_serializing_if = "Option::is_none")]
1387 simulated_positions: Option<Vec<SimulatedPosition>>,
1388 #[serde(rename = "simAsset", skip_serializing_if = "Option::is_none")]
1389 simulated_assets: Option<Vec<SimulatedAsset>>,
1390 #[serde(rename = "idxVol", skip_serializing_if = "Option::is_none")]
1391 index_volatility: Option<String>,
1392}
1393
1394impl PositionBuilderRequest {
1395 pub fn new() -> Self {
1397 Self::default()
1398 }
1399
1400 pub fn account_level(mut self, acct_lv: impl Into<String>) -> Self {
1402 self.acct_lv = Some(acct_lv.into());
1403 self
1404 }
1405
1406 pub fn include_real_positions_and_equity(mut self, include: bool) -> Self {
1408 self.include_real_positions_and_equity = Some(include);
1409 self
1410 }
1411
1412 pub fn leverage(mut self, lever: impl Into<String>) -> Self {
1414 self.lever = Some(lever.into());
1415 self
1416 }
1417
1418 pub fn greeks_type(mut self, greeks_type: impl Into<String>) -> Self {
1420 self.greeks_type = Some(greeks_type.into());
1421 self
1422 }
1423
1424 pub fn simulated_positions(mut self, simulated_positions: Vec<SimulatedPosition>) -> Self {
1426 self.simulated_positions = Some(simulated_positions);
1427 self
1428 }
1429
1430 pub fn simulated_assets(mut self, simulated_assets: Vec<SimulatedAsset>) -> Self {
1432 self.simulated_assets = Some(simulated_assets);
1433 self
1434 }
1435
1436 pub fn index_volatility(mut self, index_volatility: impl Into<String>) -> Self {
1438 self.index_volatility = Some(index_volatility.into());
1439 self
1440 }
1441}
1442
1443#[derive(Debug, Clone, Default, Serialize)]
1445pub struct PositionsHistoryRequest {
1446 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
1447 inst_type: Option<InstType>,
1448 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
1449 inst_id: Option<String>,
1450 #[serde(rename = "mgnMode", skip_serializing_if = "Option::is_none")]
1451 mgn_mode: Option<TradeMode>,
1452 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
1453 close_type: Option<String>,
1454 #[serde(rename = "posId", skip_serializing_if = "Option::is_none")]
1455 pos_id: Option<String>,
1456 #[serde(skip_serializing_if = "Option::is_none")]
1457 after: Option<String>,
1458 #[serde(skip_serializing_if = "Option::is_none")]
1459 before: Option<String>,
1460 #[serde(skip_serializing_if = "Option::is_none")]
1461 limit: Option<u32>,
1462}
1463
1464impl PositionsHistoryRequest {
1465 pub fn new() -> Self {
1467 Self::default()
1468 }
1469
1470 pub fn inst_type(mut self, inst_type: InstType) -> Self {
1472 self.inst_type = Some(inst_type);
1473 self
1474 }
1475
1476 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
1478 self.inst_id = Some(inst_id.into());
1479 self
1480 }
1481
1482 pub fn margin_mode(mut self, mgn_mode: TradeMode) -> Self {
1484 self.mgn_mode = Some(mgn_mode);
1485 self
1486 }
1487
1488 pub fn close_type(mut self, close_type: impl Into<String>) -> Self {
1490 self.close_type = Some(close_type.into());
1491 self
1492 }
1493
1494 pub fn position_id(mut self, pos_id: impl Into<String>) -> Self {
1496 self.pos_id = Some(pos_id.into());
1497 self
1498 }
1499
1500 pub fn after(mut self, after: impl Into<String>) -> Self {
1502 self.after = Some(after.into());
1503 self
1504 }
1505
1506 pub fn before(mut self, before: impl Into<String>) -> Self {
1508 self.before = Some(before.into());
1509 self
1510 }
1511
1512 pub fn limit(mut self, limit: u32) -> Self {
1514 self.limit = Some(limit);
1515 self
1516 }
1517}
1518
1519#[derive(Debug, Clone, Deserialize)]
1521#[serde(rename_all = "camelCase")]
1522#[non_exhaustive]
1523pub struct AccountBalance {
1524 #[serde(default)]
1526 pub total_eq: NumberString,
1527 #[serde(default)]
1529 pub adj_eq: NumberString,
1530 #[serde(default)]
1532 pub details: Vec<BalanceDetail>,
1533 #[serde(default)]
1535 pub u_time: NumberString,
1536}
1537
1538#[derive(Debug, Clone, Deserialize)]
1540#[serde(rename_all = "camelCase")]
1541#[non_exhaustive]
1542pub struct BalanceDetail {
1543 pub ccy: String,
1545 #[serde(default)]
1547 pub eq: NumberString,
1548 #[serde(default)]
1550 pub cash_bal: NumberString,
1551 #[serde(default)]
1553 pub avail_bal: NumberString,
1554 #[serde(default)]
1556 pub frozen_bal: NumberString,
1557}
1558
1559#[derive(Debug, Clone, Deserialize)]
1561#[serde(rename_all = "camelCase")]
1562#[non_exhaustive]
1563pub struct Position {
1564 pub inst_type: InstType,
1566 pub inst_id: String,
1568 #[serde(default)]
1570 pub pos_id: String,
1571 pub pos_side: PositionSide,
1573 pub mgn_mode: TradeMode,
1575 #[serde(default)]
1577 pub pos: NumberString,
1578 #[serde(default)]
1580 pub avg_px: NumberString,
1581 #[serde(default)]
1583 pub upl: NumberString,
1584 #[serde(default)]
1586 pub lever: NumberString,
1587 #[serde(default)]
1589 pub liq_px: NumberString,
1590}
1591
1592#[derive(Debug, Clone, Deserialize)]
1594#[serde(rename_all = "camelCase")]
1595#[non_exhaustive]
1596pub struct PositionRisk {
1597 #[serde(default)]
1599 pub adj_eq: NumberString,
1600 #[serde(default)]
1602 pub bal_data: Vec<BalanceDetail>,
1603 #[serde(default)]
1605 pub pos_data: Vec<Position>,
1606 #[serde(default)]
1608 pub ts: NumberString,
1609}
1610
1611#[derive(Debug, Clone, Deserialize)]
1613#[serde(rename_all = "camelCase")]
1614#[non_exhaustive]
1615pub struct AccountConfig {
1616 #[serde(default)]
1618 pub uid: String,
1619 #[serde(default)]
1621 pub acct_lv: String,
1622 #[serde(default)]
1624 pub pos_mode: String,
1625 #[serde(default)]
1627 pub greeks_type: String,
1628 #[serde(default)]
1630 pub auto_loan: bool,
1631}
1632
1633#[derive(Debug, Clone, Deserialize)]
1635#[serde(rename_all = "camelCase")]
1636#[non_exhaustive]
1637pub struct AccountBill {
1638 #[serde(default)]
1640 pub bill_id: String,
1641 #[serde(default)]
1643 pub inst_type: String,
1644 #[serde(default)]
1646 pub inst_id: String,
1647 #[serde(default)]
1649 pub ccy: String,
1650 #[serde(default)]
1652 pub mgn_mode: String,
1653 #[serde(rename = "type", default)]
1655 pub bill_type: String,
1656 #[serde(default)]
1658 pub sub_type: String,
1659 #[serde(default)]
1661 pub sz: NumberString,
1662 #[serde(default)]
1664 pub bal: NumberString,
1665 #[serde(default)]
1667 pub ts: NumberString,
1668}
1669
1670#[derive(Debug, Clone, Deserialize)]
1672#[serde(rename_all = "camelCase")]
1673#[non_exhaustive]
1674pub struct SetPositionModeResult {
1675 #[serde(default)]
1677 pub pos_mode: String,
1678}
1679
1680#[derive(Debug, Clone, Deserialize)]
1682#[serde(rename_all = "camelCase")]
1683#[non_exhaustive]
1684pub struct LeverageInfo {
1685 #[serde(default)]
1687 pub inst_id: String,
1688 pub mgn_mode: TradeMode,
1690 pub pos_side: PositionSide,
1692 #[serde(default)]
1694 pub lever: NumberString,
1695}
1696
1697#[derive(Debug, Clone, Deserialize)]
1699#[serde(rename_all = "camelCase")]
1700#[non_exhaustive]
1701pub struct MaxOrderSize {
1702 pub inst_id: String,
1704 #[serde(default)]
1706 pub max_buy: NumberString,
1707 #[serde(default)]
1709 pub max_sell: NumberString,
1710}
1711
1712#[derive(Debug, Clone, Deserialize)]
1714#[serde(rename_all = "camelCase")]
1715#[non_exhaustive]
1716pub struct MaxAvailableSize {
1717 pub inst_id: String,
1719 #[serde(default)]
1721 pub avail_buy: NumberString,
1722 #[serde(default)]
1724 pub avail_sell: NumberString,
1725}
1726
1727#[derive(Debug, Clone, Deserialize)]
1729#[serde(rename_all = "camelCase")]
1730#[non_exhaustive]
1731pub struct FeeRate {
1732 pub inst_type: InstType,
1734 #[serde(default)]
1736 pub inst_id: String,
1737 #[serde(default)]
1739 pub category: String,
1740 #[serde(default)]
1742 pub maker: NumberString,
1743 #[serde(default)]
1745 pub taker: NumberString,
1746 #[serde(default)]
1748 pub ts: NumberString,
1749}
1750
1751#[derive(Debug, Clone, Deserialize)]
1753#[serde(rename_all = "camelCase")]
1754#[non_exhaustive]
1755pub struct MaxWithdrawal {
1756 pub ccy: String,
1758 #[serde(default)]
1760 pub max_wd: NumberString,
1761 #[serde(default)]
1763 pub max_wd_ex: NumberString,
1764}
1765
1766#[derive(Debug, Clone, Deserialize)]
1768#[serde(rename_all = "camelCase")]
1769#[non_exhaustive]
1770pub struct PositionHistory {
1771 pub inst_type: InstType,
1773 pub inst_id: String,
1775 #[serde(default)]
1777 pub pos_id: String,
1778 pub mgn_mode: TradeMode,
1780 #[serde(rename = "type", default)]
1782 pub close_type: String,
1783 #[serde(default)]
1785 pub realized_pnl: NumberString,
1786 #[serde(default)]
1788 pub c_time: NumberString,
1789 #[serde(default)]
1791 pub u_time: NumberString,
1792}
1793
1794#[derive(Debug, Clone, Deserialize)]
1796#[serde(rename_all = "camelCase")]
1797#[non_exhaustive]
1798pub struct RiskState {
1799 #[serde(default)]
1801 pub at_risk: String,
1802 #[serde(default)]
1804 pub ts: NumberString,
1805}
1806
1807#[derive(Debug, Clone, Deserialize)]
1809#[serde(rename_all = "camelCase")]
1810#[non_exhaustive]
1811pub struct AdjustMarginResult {
1812 #[serde(default)]
1814 pub inst_id: String,
1815 #[serde(default)]
1817 pub pos_side: String,
1818 #[serde(default)]
1820 pub amt: NumberString,
1821 #[serde(rename = "type", default)]
1823 pub adjustment_type: String,
1824}
1825
1826#[derive(Debug, Clone, Deserialize)]
1828#[serde(rename_all = "camelCase")]
1829#[non_exhaustive]
1830pub struct AccountInstrument {
1831 #[serde(default)]
1833 pub inst_type: String,
1834 #[serde(default)]
1836 pub inst_id: String,
1837 #[serde(default)]
1839 pub uly: String,
1840 #[serde(default)]
1842 pub inst_family: String,
1843 #[serde(default)]
1845 pub base_ccy: String,
1846 #[serde(default)]
1848 pub quote_ccy: String,
1849 #[serde(default)]
1851 pub settle_ccy: String,
1852}
1853
1854#[derive(Debug, Clone, Deserialize)]
1856#[serde(rename_all = "camelCase")]
1857#[non_exhaustive]
1858pub struct MaxLoan {
1859 #[serde(default)]
1861 pub inst_id: String,
1862 #[serde(default)]
1864 pub mgn_mode: String,
1865 #[serde(default)]
1867 pub mgn_ccy: String,
1868 #[serde(default)]
1870 pub max_loan: NumberString,
1871}
1872
1873#[derive(Debug, Clone, Deserialize)]
1875#[serde(rename_all = "camelCase")]
1876#[non_exhaustive]
1877pub struct InterestAccrued {
1878 #[serde(default)]
1880 pub inst_id: String,
1881 #[serde(default)]
1883 pub ccy: String,
1884 #[serde(default)]
1886 pub mgn_mode: String,
1887 #[serde(default)]
1889 pub interest: NumberString,
1890 #[serde(default)]
1892 pub interest_rate: NumberString,
1893 #[serde(default)]
1895 pub liab: NumberString,
1896 #[serde(default)]
1898 pub ts: NumberString,
1899}
1900
1901#[derive(Debug, Clone, Deserialize)]
1903#[serde(rename_all = "camelCase")]
1904#[non_exhaustive]
1905pub struct InterestRate {
1906 #[serde(default)]
1908 pub ccy: String,
1909 #[serde(default)]
1911 pub interest_rate: NumberString,
1912}
1913
1914#[derive(Debug, Clone, Deserialize)]
1916#[serde(rename_all = "camelCase")]
1917#[non_exhaustive]
1918pub struct SetGreeksResult {
1919 #[serde(default)]
1921 pub greeks_type: String,
1922}
1923
1924#[derive(Debug, Clone, Deserialize)]
1926#[serde(rename_all = "camelCase")]
1927#[non_exhaustive]
1928pub struct SetIsolatedModeResult {
1929 #[serde(default)]
1931 pub iso_mode: String,
1932 #[serde(rename = "type", default)]
1934 pub mode_type: String,
1935}
1936
1937#[derive(Debug, Clone, Deserialize)]
1939#[serde(rename_all = "camelCase")]
1940#[non_exhaustive]
1941pub struct BorrowRepayResult {
1942 #[serde(default)]
1944 pub ccy: String,
1945 #[serde(default)]
1947 pub side: String,
1948 #[serde(default)]
1950 pub amt: NumberString,
1951 #[serde(default)]
1953 pub ord_id: String,
1954}
1955
1956#[derive(Debug, Clone, Deserialize)]
1958#[serde(rename_all = "camelCase")]
1959#[non_exhaustive]
1960pub struct BorrowRepayHistory {
1961 #[serde(default)]
1963 pub ccy: String,
1964 #[serde(default)]
1966 pub side: String,
1967 #[serde(default)]
1969 pub amt: NumberString,
1970 #[serde(default)]
1972 pub ord_id: String,
1973 #[serde(default)]
1975 pub state: String,
1976 #[serde(default)]
1978 pub ts: NumberString,
1979}
1980
1981#[derive(Debug, Clone, Deserialize)]
1983#[serde(rename_all = "camelCase")]
1984#[non_exhaustive]
1985pub struct InterestLimit {
1986 #[serde(default)]
1988 pub ccy: String,
1989 #[serde(default)]
1991 pub rate: NumberString,
1992 #[serde(default)]
1994 pub loan_quota: NumberString,
1995 #[serde(default)]
1997 pub used_loan: NumberString,
1998}
1999
2000#[derive(Debug, Clone, Deserialize)]
2002#[serde(rename_all = "camelCase")]
2003#[non_exhaustive]
2004pub struct SimulatedMargin {
2005 #[serde(default)]
2007 pub imr: NumberString,
2008 #[serde(default)]
2010 pub mmr: NumberString,
2011 #[serde(default)]
2013 pub mr: NumberString,
2014 #[serde(default)]
2016 pub notional_usd: NumberString,
2017 #[serde(default)]
2019 pub details: Vec<SimulatedMarginDetail>,
2020}
2021
2022#[derive(Debug, Clone, Deserialize)]
2024#[serde(rename_all = "camelCase")]
2025#[non_exhaustive]
2026pub struct SimulatedMarginDetail {
2027 #[serde(default)]
2029 pub inst_id: String,
2030 #[serde(default)]
2032 pub pos: NumberString,
2033 #[serde(default)]
2035 pub imr: NumberString,
2036 #[serde(default)]
2038 pub mmr: NumberString,
2039 #[serde(default)]
2041 pub upl: NumberString,
2042}
2043
2044#[derive(Debug, Clone, Deserialize)]
2046#[serde(rename_all = "camelCase")]
2047#[non_exhaustive]
2048pub struct Greek {
2049 #[serde(default)]
2051 pub ccy: String,
2052 #[serde(rename = "deltaBS", default)]
2054 pub delta_bs: NumberString,
2055 #[serde(rename = "deltaPA", default)]
2057 pub delta_pa: NumberString,
2058 #[serde(rename = "gammaBS", default)]
2060 pub gamma_bs: NumberString,
2061 #[serde(rename = "thetaBS", default)]
2063 pub theta_bs: NumberString,
2064 #[serde(rename = "vegaBS", default)]
2066 pub vega_bs: NumberString,
2067}
2068
2069#[derive(Debug, Clone, Deserialize)]
2071#[serde(rename_all = "camelCase")]
2072#[non_exhaustive]
2073pub struct AccountPositionTier {
2074 #[serde(default)]
2076 pub inst_type: String,
2077 #[serde(default)]
2079 pub uly: String,
2080 #[serde(default)]
2082 pub inst_family: String,
2083 #[serde(default)]
2085 pub pos_type: String,
2086 #[serde(default)]
2088 pub min_sz: NumberString,
2089 #[serde(default)]
2091 pub max_sz: NumberString,
2092}
2093
2094#[derive(Debug, Clone, Deserialize)]
2096#[serde(rename_all = "camelCase")]
2097#[non_exhaustive]
2098pub struct SetRiskOffsetTypeResult {
2099 #[serde(rename = "type", default)]
2101 pub risk_offset_type: String,
2102}
2103
2104#[derive(Debug, Clone, Deserialize)]
2106#[serde(rename_all = "camelCase")]
2107#[non_exhaustive]
2108pub struct SetAutoLoanResult {
2109 #[serde(default)]
2111 pub auto_loan: String,
2112}
2113
2114#[derive(Debug, Clone, Deserialize)]
2116#[serde(rename_all = "camelCase")]
2117#[non_exhaustive]
2118pub struct SetAccountLevelResult {
2119 #[serde(default)]
2121 pub acct_lv: String,
2122}
2123
2124#[derive(Debug, Clone, Deserialize)]
2126#[serde(rename_all = "camelCase")]
2127#[non_exhaustive]
2128pub struct ActivateOptionResult {
2129 #[serde(default)]
2131 pub result: String,
2132}
2133
2134#[derive(Debug, Clone, Deserialize)]
2136#[serde(rename_all = "camelCase")]
2137#[non_exhaustive]
2138pub struct PositionBuilderResult {
2139 #[serde(default)]
2141 pub acct_lv: String,
2142 #[serde(default)]
2144 pub adj_eq: NumberString,
2145 #[serde(default)]
2147 pub imr: NumberString,
2148 #[serde(default)]
2150 pub mmr: NumberString,
2151 #[serde(default)]
2153 pub mr: NumberString,
2154 #[serde(default)]
2156 pub pos_data: Vec<PositionBuilderPosition>,
2157 #[serde(default)]
2159 pub asset_data: Vec<PositionBuilderAsset>,
2160}
2161
2162#[derive(Debug, Clone, Deserialize)]
2164#[serde(rename_all = "camelCase")]
2165#[non_exhaustive]
2166pub struct PositionBuilderPosition {
2167 #[serde(default)]
2169 pub inst_type: String,
2170 #[serde(default)]
2172 pub inst_id: String,
2173 #[serde(default)]
2175 pub pos: NumberString,
2176 #[serde(default)]
2178 pub avg_px: NumberString,
2179 #[serde(default)]
2181 pub upl: NumberString,
2182}
2183
2184#[derive(Debug, Clone, Deserialize)]
2186#[serde(rename_all = "camelCase")]
2187#[non_exhaustive]
2188pub struct PositionBuilderAsset {
2189 #[serde(default)]
2191 pub ccy: String,
2192 #[serde(default)]
2194 pub eq: NumberString,
2195}
2196
2197#[cfg(test)]
2198mod tests {
2199 use crate::test_util::MockTransport;
2200 use crate::{Credentials, OkxClient};
2201
2202 fn signed_client(mock: MockTransport) -> OkxClient<MockTransport> {
2203 OkxClient::with_transport(mock)
2204 .credentials(Credentials::new("key", "secret", "pass"))
2205 .build()
2206 }
2207
2208 #[tokio::test]
2209 async fn get_balance_signs_request_and_parses() {
2210 let body = r#"{"code":"0","msg":"","data":[
2211 {"totalEq":"10000","adjEq":"9500","uTime":"1597026383085","details":[
2212 {"ccy":"USDT","eq":"10000","cashBal":"10000","availBal":"9000","frozenBal":"1000"}]}]}"#;
2213 let mock = MockTransport::new(body);
2214 let client = signed_client(mock.clone());
2215
2216 let balances = client.account().get_balance(None).await.unwrap();
2217 assert_eq!(balances[0].total_eq.as_str(), "10000");
2218 assert_eq!(balances[0].details[0].ccy, "USDT");
2219 assert_eq!(balances[0].details[0].avail_bal.as_str(), "9000");
2220
2221 let req = mock.captured();
2222 assert_eq!(req.method, http::Method::GET);
2223 assert!(req.uri.ends_with("/api/v5/account/balance"));
2224 assert!(req.is_signed(), "authenticated endpoint must be signed");
2225 }
2226
2227 #[tokio::test]
2228 async fn get_positions_passes_filters() {
2229 let body = r#"{"code":"0","msg":"","data":[
2230 {"instType":"SWAP","instId":"BTC-USDT-SWAP","posId":"1","posSide":"long",
2231 "mgnMode":"cross","pos":"1","avgPx":"42000","upl":"5","lever":"10","liqPx":"38000"}]}"#;
2232 let mock = MockTransport::new(body);
2233 let client = signed_client(mock.clone());
2234
2235 let positions = client
2236 .account()
2237 .get_positions(Some(crate::model::InstType::Swap), Some("BTC-USDT-SWAP"))
2238 .await
2239 .unwrap();
2240 assert_eq!(positions[0].inst_id, "BTC-USDT-SWAP");
2241 assert_eq!(positions[0].pos_side, crate::model::PositionSide::Long);
2242
2243 let req = mock.captured();
2244 assert_eq!(req.query(), Some("instType=SWAP&instId=BTC-USDT-SWAP"));
2245 assert!(req.is_signed());
2246 }
2247
2248 #[tokio::test]
2249 async fn missing_credentials_is_configuration_error() {
2250 let mock = MockTransport::new("{}");
2251 let client = OkxClient::with_transport(mock).build();
2252 let err = client.account().get_balance(None).await.unwrap_err();
2253 assert!(matches!(err, crate::Error::Configuration(_)));
2254 }
2255
2256 #[tokio::test]
2257 async fn get_position_risk_signs_and_parses() {
2258 let body = r#"{"code":"0","msg":"","data":[
2259 {"adjEq":"1000","ts":"1597026383085","balData":[{"ccy":"USDT","eq":"1000"}],
2260 "posData":[{"instType":"SWAP","instId":"BTC-USDT-SWAP","posSide":"long","mgnMode":"cross","pos":"1"}]}]}"#;
2261 let mock = MockTransport::new(body);
2262 let client = signed_client(mock.clone());
2263
2264 let risk = client
2265 .account()
2266 .get_position_risk(Some(crate::model::InstType::Swap))
2267 .await
2268 .unwrap();
2269 assert_eq!(risk[0].adj_eq.as_str(), "1000");
2270 assert_eq!(risk[0].pos_data[0].inst_id, "BTC-USDT-SWAP");
2271
2272 let req = mock.captured();
2273 assert_eq!(req.query(), Some("instType=SWAP"));
2274 assert!(req.is_signed());
2275 }
2276
2277 #[tokio::test]
2278 async fn get_account_config_signs_and_parses() {
2279 let body = r#"{"code":"0","msg":"","data":[
2280 {"uid":"1","acctLv":"2","posMode":"net_mode","greeksType":"PA","autoLoan":false}]}"#;
2281 let mock = MockTransport::new(body);
2282 let client = signed_client(mock.clone());
2283
2284 let config = client.account().get_account_config().await.unwrap();
2285 assert_eq!(config[0].pos_mode, "net_mode");
2286
2287 let req = mock.captured();
2288 assert!(req.uri.ends_with("/api/v5/account/config"));
2289 assert_eq!(req.query(), None);
2290 assert!(req.is_signed());
2291 }
2292
2293 #[tokio::test]
2294 async fn get_account_bills_uses_builder_query() {
2295 let body = r#"{"code":"0","msg":"","data":[
2296 {"billId":"1","instType":"SPOT","ccy":"USDT","mgnMode":"cash","type":"1","subType":"2",
2297 "sz":"10","bal":"100","ts":"1597026383085"}]}"#;
2298 let mock = MockTransport::new(body);
2299 let client = signed_client(mock.clone());
2300 let request = super::BillsRequest::new()
2301 .inst_type(crate::model::InstType::Spot)
2302 .currency("USDT")
2303 .bill_type("1")
2304 .limit(1);
2305
2306 let bills = client.account().get_account_bills(&request).await.unwrap();
2307 assert_eq!(bills[0].bill_id, "1");
2308
2309 let req = mock.captured();
2310 assert_eq!(req.query(), Some("instType=SPOT&ccy=USDT&type=1&limit=1"));
2311 assert!(req.is_signed());
2312 }
2313
2314 #[tokio::test]
2315 async fn get_account_bills_archive_uses_builder_query() {
2316 let body = r#"{"code":"0","msg":"","data":[
2317 {"billId":"2","instType":"SPOT","ccy":"USDT","type":"1","sz":"10","bal":"100","ts":"1597026383085"}]}"#;
2318 let mock = MockTransport::new(body);
2319 let client = signed_client(mock.clone());
2320 let request = super::BillsArchiveRequest::new()
2321 .filters(super::BillsRequest::new().currency("USDT"))
2322 .begin("100")
2323 .end("200");
2324
2325 let bills = client
2326 .account()
2327 .get_account_bills_archive(&request)
2328 .await
2329 .unwrap();
2330 assert_eq!(bills[0].bill_id, "2");
2331
2332 let req = mock.captured();
2333 assert_eq!(req.query(), Some("ccy=USDT&begin=100&end=200"));
2334 assert!(req.is_signed());
2335 }
2336
2337 #[tokio::test]
2338 async fn set_position_mode_posts_body() {
2339 let body = r#"{"code":"0","msg":"","data":[{"posMode":"net_mode"}]}"#;
2340 let mock = MockTransport::new(body);
2341 let client = signed_client(mock.clone());
2342
2343 let result = client.account().set_position_mode("net_mode").await.unwrap();
2344 assert_eq!(result[0].pos_mode, "net_mode");
2345
2346 let req = mock.captured();
2347 assert_eq!(req.method, http::Method::POST);
2348 assert!(req.uri.ends_with("/api/v5/account/set-position-mode"));
2349 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2350 assert_eq!(sent["posMode"], "net_mode");
2351 assert!(req.is_signed());
2352 }
2353
2354 #[tokio::test]
2355 async fn set_leverage_posts_builder_body() {
2356 let body = r#"{"code":"0","msg":"","data":[
2357 {"instId":"BTC-USDT-SWAP","mgnMode":"cross","posSide":"long","lever":"10"}]}"#;
2358 let mock = MockTransport::new(body);
2359 let client = signed_client(mock.clone());
2360 let request = super::SetLeverageRequest::new("10", crate::model::TradeMode::Cross)
2361 .inst_id("BTC-USDT-SWAP")
2362 .position_side(crate::model::PositionSide::Long);
2363
2364 let result = client.account().set_leverage(&request).await.unwrap();
2365 assert_eq!(result[0].lever.as_str(), "10");
2366
2367 let req = mock.captured();
2368 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2369 assert_eq!(sent["lever"], "10");
2370 assert_eq!(sent["mgnMode"], "cross");
2371 assert_eq!(sent["instId"], "BTC-USDT-SWAP");
2372 assert_eq!(sent["posSide"], "long");
2373 assert!(req.is_signed());
2374 }
2375
2376 #[tokio::test]
2377 async fn get_leverage_uses_builder_query() {
2378 let body = r#"{"code":"0","msg":"","data":[
2379 {"instId":"BTC-USDT-SWAP","mgnMode":"cross","posSide":"long","lever":"10"}]}"#;
2380 let mock = MockTransport::new(body);
2381 let client = signed_client(mock.clone());
2382 let request = super::LeverageRequest::new(crate::model::TradeMode::Cross)
2383 .inst_id("BTC-USDT-SWAP");
2384
2385 let result = client.account().get_leverage(&request).await.unwrap();
2386 assert_eq!(result[0].mgn_mode, crate::model::TradeMode::Cross);
2387
2388 let req = mock.captured();
2389 assert_eq!(req.query(), Some("mgnMode=cross&instId=BTC-USDT-SWAP"));
2390 assert!(req.is_signed());
2391 }
2392
2393 #[tokio::test]
2394 async fn get_max_order_size_uses_builder_query() {
2395 let body = r#"{"code":"0","msg":"","data":[
2396 {"instId":"BTC-USDT","maxBuy":"1","maxSell":"2"}]}"#;
2397 let mock = MockTransport::new(body);
2398 let client = signed_client(mock.clone());
2399 let request = super::MaxOrderSizeRequest::new("BTC-USDT", crate::model::TradeMode::Cash)
2400 .price("42000");
2401
2402 let result = client.account().get_max_order_size(&request).await.unwrap();
2403 assert_eq!(result[0].max_buy.as_str(), "1");
2404
2405 let req = mock.captured();
2406 assert_eq!(req.query(), Some("instId=BTC-USDT&tdMode=cash&px=42000"));
2407 assert!(req.is_signed());
2408 }
2409
2410 #[tokio::test]
2411 async fn get_max_avail_size_uses_builder_query() {
2412 let body = r#"{"code":"0","msg":"","data":[
2413 {"instId":"BTC-USDT","availBuy":"1","availSell":"2"}]}"#;
2414 let mock = MockTransport::new(body);
2415 let client = signed_client(mock.clone());
2416 let request = super::MaxAvailableSizeRequest::new("BTC-USDT", crate::model::TradeMode::Cash)
2417 .reduce_only(false);
2418
2419 let result = client.account().get_max_avail_size(&request).await.unwrap();
2420 assert_eq!(result[0].avail_sell.as_str(), "2");
2421
2422 let req = mock.captured();
2423 assert_eq!(
2424 req.query(),
2425 Some("instId=BTC-USDT&tdMode=cash&reduceOnly=false")
2426 );
2427 assert!(req.is_signed());
2428 }
2429
2430 #[tokio::test]
2431 async fn get_fee_rates_uses_builder_query() {
2432 let body = r#"{"code":"0","msg":"","data":[
2433 {"instType":"SPOT","instId":"BTC-USDT","category":"1","maker":"-0.0001","taker":"0.001","ts":"1597026383085"}]}"#;
2434 let mock = MockTransport::new(body);
2435 let client = signed_client(mock.clone());
2436 let request = super::FeeRatesRequest::new(crate::model::InstType::Spot)
2437 .inst_id("BTC-USDT");
2438
2439 let result = client.account().get_fee_rates(&request).await.unwrap();
2440 assert_eq!(result[0].maker.as_str(), "-0.0001");
2441
2442 let req = mock.captured();
2443 assert_eq!(req.query(), Some("instType=SPOT&instId=BTC-USDT"));
2444 assert!(req.is_signed());
2445 }
2446
2447 #[tokio::test]
2448 async fn get_max_withdrawal_queries_currency() {
2449 let body = r#"{"code":"0","msg":"","data":[
2450 {"ccy":"USDT","maxWd":"100","maxWdEx":"90"}]}"#;
2451 let mock = MockTransport::new(body);
2452 let client = signed_client(mock.clone());
2453
2454 let result = client.account().get_max_withdrawal(Some("USDT")).await.unwrap();
2455 assert_eq!(result[0].max_wd.as_str(), "100");
2456
2457 let req = mock.captured();
2458 assert_eq!(req.query(), Some("ccy=USDT"));
2459 assert!(req.is_signed());
2460 }
2461
2462 #[tokio::test]
2463 async fn get_positions_history_uses_builder_query() {
2464 let body = r#"{"code":"0","msg":"","data":[
2465 {"instType":"SWAP","instId":"BTC-USDT-SWAP","posId":"1","mgnMode":"cross",
2466 "type":"2","realizedPnl":"5","cTime":"1597026383085","uTime":"1597026383999"}]}"#;
2467 let mock = MockTransport::new(body);
2468 let client = signed_client(mock.clone());
2469 let request = super::PositionsHistoryRequest::new()
2470 .inst_type(crate::model::InstType::Swap)
2471 .inst_id("BTC-USDT-SWAP")
2472 .limit(1);
2473
2474 let result = client.account().get_positions_history(&request).await.unwrap();
2475 assert_eq!(result[0].realized_pnl.as_str(), "5");
2476
2477 let req = mock.captured();
2478 assert_eq!(
2479 req.query(),
2480 Some("instType=SWAP&instId=BTC-USDT-SWAP&limit=1")
2481 );
2482 assert!(req.is_signed());
2483 }
2484
2485 #[tokio::test]
2486 async fn get_risk_state_signs_and_parses() {
2487 let body = r#"{"code":"0","msg":"","data":[{"atRisk":"false","ts":"1597026383085"}]}"#;
2488 let mock = MockTransport::new(body);
2489 let client = signed_client(mock.clone());
2490
2491 let result = client.account().get_risk_state().await.unwrap();
2492 assert_eq!(result[0].at_risk, "false");
2493
2494 let req = mock.captured();
2495 assert!(req.uri.ends_with("/api/v5/account/risk-state"));
2496 assert_eq!(req.query(), None);
2497 assert!(req.is_signed());
2498 }
2499
2500 #[tokio::test]
2501 async fn demo_trading_sets_simulated_header() {
2502 let body = r#"{"code":"0","msg":"","data":[
2503 {"uid":"1","acctLv":"2","posMode":"net_mode","greeksType":"PA","autoLoan":false}]}"#;
2504 let mock = MockTransport::new(body);
2505 let client = OkxClient::with_transport(mock.clone())
2506 .credentials(Credentials::new("key", "secret", "pass"))
2507 .demo_trading(true)
2508 .build();
2509
2510 let config = client.account().get_account_config().await.unwrap();
2511 assert_eq!(config[0].acct_lv, "2");
2512
2513 let req = mock.captured();
2514 assert_eq!(
2515 req.headers
2516 .get("x-simulated-trading")
2517 .and_then(|v| v.to_str().ok()),
2518 Some("1")
2519 );
2520 assert!(req.is_signed());
2521 }
2522
2523 #[tokio::test]
2524 async fn adjust_margin_posts_body() {
2525 let body = r#"{"code":"0","msg":"","data":[
2526 {"instId":"BTC-USDT-SWAP","posSide":"long","type":"add","amt":"100"}]}"#;
2527 let mock = MockTransport::new(body);
2528 let client = signed_client(mock.clone());
2529 let request = super::AdjustMarginRequest::new(
2530 "BTC-USDT-SWAP",
2531 crate::model::PositionSide::Long,
2532 "add",
2533 "100",
2534 )
2535 .loan_transfer(true);
2536
2537 let result = client.account().adjust_margin(&request).await.unwrap();
2538 assert_eq!(result[0].amt.as_str(), "100");
2539
2540 let req = mock.captured();
2541 assert_eq!(req.method, http::Method::POST);
2542 assert!(req.uri.ends_with("/api/v5/account/position/margin-balance"));
2543 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2544 assert_eq!(sent["instId"], "BTC-USDT-SWAP");
2545 assert_eq!(sent["posSide"], "long");
2546 assert_eq!(sent["type"], "add");
2547 assert_eq!(sent["amt"], "100");
2548 assert_eq!(sent["loanTrans"], true);
2549 assert!(req.is_signed());
2550 }
2551
2552 #[tokio::test]
2553 async fn get_account_instruments_uses_builder_query() {
2554 let body = r#"{"code":"0","msg":"","data":[
2555 {"instType":"SWAP","instId":"BTC-USDT-SWAP","uly":"BTC-USDT","instFamily":"BTC-USDT",
2556 "baseCcy":"BTC","quoteCcy":"USDT","settleCcy":"USDT"}]}"#;
2557 let mock = MockTransport::new(body);
2558 let client = signed_client(mock.clone());
2559 let request = super::AccountInstrumentsRequest::new()
2560 .inst_type(crate::model::InstType::Swap)
2561 .inst_id("BTC-USDT-SWAP");
2562
2563 let result = client
2564 .account()
2565 .get_account_instruments(&request)
2566 .await
2567 .unwrap();
2568 assert_eq!(result[0].settle_ccy, "USDT");
2569
2570 let req = mock.captured();
2571 assert_eq!(req.query(), Some("instType=SWAP&instId=BTC-USDT-SWAP"));
2572 assert!(req.is_signed());
2573 }
2574
2575 #[tokio::test]
2576 async fn get_max_loan_uses_builder_query() {
2577 let body = r#"{"code":"0","msg":"","data":[
2578 {"instId":"BTC-USDT","mgnMode":"cross","mgnCcy":"USDT","maxLoan":"1000"}]}"#;
2579 let mock = MockTransport::new(body);
2580 let client = signed_client(mock.clone());
2581 let request = super::MaxLoanRequest::new("BTC-USDT", crate::model::TradeMode::Cross)
2582 .margin_currency("USDT");
2583
2584 let result = client.account().get_max_loan(&request).await.unwrap();
2585 assert_eq!(result[0].max_loan.as_str(), "1000");
2586
2587 let req = mock.captured();
2588 assert_eq!(req.query(), Some("instId=BTC-USDT&mgnMode=cross&mgnCcy=USDT"));
2589 assert!(req.is_signed());
2590 }
2591
2592 #[tokio::test]
2593 async fn get_interest_accrued_uses_builder_query() {
2594 let body = r#"{"code":"0","msg":"","data":[
2595 {"instId":"BTC-USDT","ccy":"USDT","mgnMode":"cross","interest":"1",
2596 "interestRate":"0.0001","liab":"100","ts":"1597026383085"}]}"#;
2597 let mock = MockTransport::new(body);
2598 let client = signed_client(mock.clone());
2599 let request = super::InterestAccruedRequest::new()
2600 .inst_id("BTC-USDT")
2601 .currency("USDT")
2602 .limit(1);
2603
2604 let result = client.account().get_interest_accrued(&request).await.unwrap();
2605 assert_eq!(result[0].interest_rate.as_str(), "0.0001");
2606
2607 let req = mock.captured();
2608 assert_eq!(req.query(), Some("instId=BTC-USDT&ccy=USDT&limit=1"));
2609 assert!(req.is_signed());
2610 }
2611
2612 #[tokio::test]
2613 async fn get_interest_rate_queries_currency() {
2614 let body = r#"{"code":"0","msg":"","data":[{"ccy":"USDT","interestRate":"0.0001"}]}"#;
2615 let mock = MockTransport::new(body);
2616 let client = signed_client(mock.clone());
2617
2618 let result = client.account().get_interest_rate(Some("USDT")).await.unwrap();
2619 assert_eq!(result[0].ccy, "USDT");
2620
2621 let req = mock.captured();
2622 assert_eq!(req.query(), Some("ccy=USDT"));
2623 assert!(req.is_signed());
2624 }
2625
2626 #[tokio::test]
2627 async fn set_greeks_posts_body() {
2628 let body = r#"{"code":"0","msg":"","data":[{"greeksType":"PA"}]}"#;
2629 let mock = MockTransport::new(body);
2630 let client = signed_client(mock.clone());
2631
2632 let result = client.account().set_greeks("PA").await.unwrap();
2633 assert_eq!(result[0].greeks_type, "PA");
2634
2635 let req = mock.captured();
2636 assert_eq!(req.method, http::Method::POST);
2637 assert!(req.uri.ends_with("/api/v5/account/set-greeks"));
2638 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2639 assert_eq!(sent["greeksType"], "PA");
2640 assert!(req.is_signed());
2641 }
2642
2643 #[tokio::test]
2644 async fn set_isolated_mode_posts_body() {
2645 let body = r#"{"code":"0","msg":"","data":[{"isoMode":"automatic","type":"MARGIN"}]}"#;
2646 let mock = MockTransport::new(body);
2647 let client = signed_client(mock.clone());
2648
2649 let result = client
2650 .account()
2651 .set_isolated_mode("automatic", "MARGIN")
2652 .await
2653 .unwrap();
2654 assert_eq!(result[0].iso_mode, "automatic");
2655
2656 let req = mock.captured();
2657 assert_eq!(req.method, http::Method::POST);
2658 assert!(req.uri.ends_with("/api/v5/account/set-isolated-mode"));
2659 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2660 assert_eq!(sent["isoMode"], "automatic");
2661 assert_eq!(sent["type"], "MARGIN");
2662 assert!(req.is_signed());
2663 }
2664
2665 #[tokio::test]
2666 async fn borrow_repay_posts_body() {
2667 let body = r#"{"code":"0","msg":"","data":[
2668 {"ccy":"USDT","side":"borrow","amt":"100","ordId":"1"}]}"#;
2669 let mock = MockTransport::new(body);
2670 let client = signed_client(mock.clone());
2671 let request = super::BorrowRepayRequest::new("USDT", "borrow", "100").order_id("1");
2672
2673 let result = client.account().borrow_repay(&request).await.unwrap();
2674 assert_eq!(result[0].ord_id, "1");
2675
2676 let req = mock.captured();
2677 assert_eq!(req.method, http::Method::POST);
2678 assert!(req.uri.ends_with("/api/v5/account/borrow-repay"));
2679 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2680 assert_eq!(sent["ccy"], "USDT");
2681 assert_eq!(sent["side"], "borrow");
2682 assert_eq!(sent["amt"], "100");
2683 assert_eq!(sent["ordId"], "1");
2684 assert!(req.is_signed());
2685 }
2686
2687 #[tokio::test]
2688 async fn get_borrow_repay_history_uses_builder_query() {
2689 let body = r#"{"code":"0","msg":"","data":[
2690 {"ccy":"USDT","side":"borrow","amt":"100","ordId":"1","state":"2","ts":"1597026383085"}]}"#;
2691 let mock = MockTransport::new(body);
2692 let client = signed_client(mock.clone());
2693 let request = super::BorrowRepayHistoryRequest::new()
2694 .currency("USDT")
2695 .limit(1);
2696
2697 let result = client
2698 .account()
2699 .get_borrow_repay_history(&request)
2700 .await
2701 .unwrap();
2702 assert_eq!(result[0].state, "2");
2703
2704 let req = mock.captured();
2705 assert_eq!(req.query(), Some("ccy=USDT&limit=1"));
2706 assert!(req.is_signed());
2707 }
2708
2709 #[tokio::test]
2710 async fn get_interest_limits_uses_builder_query() {
2711 let body = r#"{"code":"0","msg":"","data":[
2712 {"ccy":"USDT","rate":"0.0001","loanQuota":"1000","usedLoan":"100"}]}"#;
2713 let mock = MockTransport::new(body);
2714 let client = signed_client(mock.clone());
2715 let request = super::InterestLimitsRequest::new()
2716 .limit_type("1")
2717 .currency("USDT");
2718
2719 let result = client.account().get_interest_limits(&request).await.unwrap();
2720 assert_eq!(result[0].loan_quota.as_str(), "1000");
2721
2722 let req = mock.captured();
2723 assert_eq!(req.query(), Some("type=1&ccy=USDT"));
2724 assert!(req.is_signed());
2725 }
2726
2727 #[tokio::test]
2728 async fn get_simulated_margin_posts_body_and_omits_unset_fields() {
2729 let body = r#"{"code":"0","msg":"","data":[
2730 {"imr":"10","mmr":"5","mr":"100","notionalUsd":"1000",
2731 "details":[{"instId":"BTC-USDT-SWAP","pos":"1","imr":"10","mmr":"5","upl":"2"}]}]}"#;
2732 let mock = MockTransport::new(body);
2733 let client = signed_client(mock.clone());
2734 let request = super::SimulatedMarginRequest::new()
2735 .inst_type(crate::model::InstType::Swap)
2736 .simulated_positions(vec![
2737 super::SimulatedPosition::new("BTC-USDT-SWAP")
2738 .position("1")
2739 .leverage("10"),
2740 ]);
2741
2742 let result = client.account().get_simulated_margin(&request).await.unwrap();
2743 assert_eq!(result[0].details[0].upl.as_str(), "2");
2744
2745 let req = mock.captured();
2746 assert_eq!(req.method, http::Method::POST);
2747 assert!(req.uri.ends_with("/api/v5/account/simulated_margin"));
2748 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2749 assert_eq!(sent["instType"], "SWAP");
2750 assert_eq!(sent["simPos"][0]["instId"], "BTC-USDT-SWAP");
2751 assert_eq!(sent["simPos"][0]["pos"], "1");
2752 assert_eq!(sent["simPos"][0]["lever"], "10");
2753 assert!(sent.get("inclRealPos").is_none());
2754 assert!(sent["simPos"][0].get("avgPx").is_none());
2755 assert!(req.is_signed());
2756 }
2757
2758 #[tokio::test]
2759 async fn get_greeks_queries_currency() {
2760 let body = r#"{"code":"0","msg":"","data":[
2761 {"ccy":"BTC","deltaBS":"1","deltaPA":"0.9","gammaBS":"0.1","thetaBS":"-0.01","vegaBS":"2"}]}"#;
2762 let mock = MockTransport::new(body);
2763 let client = signed_client(mock.clone());
2764
2765 let result = client.account().get_greeks(Some("BTC")).await.unwrap();
2766 assert_eq!(result[0].delta_pa.as_str(), "0.9");
2767
2768 let req = mock.captured();
2769 assert_eq!(req.query(), Some("ccy=BTC"));
2770 assert!(req.is_signed());
2771 }
2772
2773 #[tokio::test]
2774 async fn get_account_position_tiers_uses_builder_query() {
2775 let body = r#"{"code":"0","msg":"","data":[
2776 {"instType":"OPTION","uly":"BTC-USD","instFamily":"BTC-USD","posType":"1","minSz":"0","maxSz":"100"}]}"#;
2777 let mock = MockTransport::new(body);
2778 let client = signed_client(mock.clone());
2779 let request = super::AccountPositionTiersRequest::new()
2780 .inst_type(crate::model::InstType::Option)
2781 .underlying("BTC-USD");
2782
2783 let result = client
2784 .account()
2785 .get_account_position_tiers(&request)
2786 .await
2787 .unwrap();
2788 assert_eq!(result[0].max_sz.as_str(), "100");
2789
2790 let req = mock.captured();
2791 assert_eq!(req.query(), Some("instType=OPTION&uly=BTC-USD"));
2792 assert!(req.is_signed());
2793 }
2794
2795 #[tokio::test]
2796 async fn set_risk_offset_type_posts_body() {
2797 let body = r#"{"code":"0","msg":"","data":[{"type":"1"}]}"#;
2798 let mock = MockTransport::new(body);
2799 let client = signed_client(mock.clone());
2800
2801 let result = client.account().set_risk_offset_type("1").await.unwrap();
2802 assert_eq!(result[0].risk_offset_type, "1");
2803
2804 let req = mock.captured();
2805 assert_eq!(req.method, http::Method::POST);
2806 assert!(req.uri.ends_with("/api/v5/account/set-riskOffset-type"));
2807 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2808 assert_eq!(sent["type"], "1");
2809 assert!(req.is_signed());
2810 }
2811
2812 #[tokio::test]
2813 async fn set_auto_loan_posts_body() {
2814 let body = r#"{"code":"0","msg":"","data":[{"autoLoan":"true"}]}"#;
2815 let mock = MockTransport::new(body);
2816 let client = signed_client(mock.clone());
2817
2818 let result = client.account().set_auto_loan(true).await.unwrap();
2819 assert_eq!(result[0].auto_loan, "true");
2820
2821 let req = mock.captured();
2822 assert_eq!(req.method, http::Method::POST);
2823 assert!(req.uri.ends_with("/api/v5/account/set-auto-loan"));
2824 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2825 assert_eq!(sent["autoLoan"], true);
2826 assert!(req.is_signed());
2827 }
2828
2829 #[tokio::test]
2830 async fn set_account_level_posts_body() {
2831 let body = r#"{"code":"0","msg":"","data":[{"acctLv":"2"}]}"#;
2832 let mock = MockTransport::new(body);
2833 let client = signed_client(mock.clone());
2834
2835 let result = client.account().set_account_level("2").await.unwrap();
2836 assert_eq!(result[0].acct_lv, "2");
2837
2838 let req = mock.captured();
2839 assert_eq!(req.method, http::Method::POST);
2840 assert!(req.uri.ends_with("/api/v5/account/set-account-level"));
2841 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2842 assert_eq!(sent["acctLv"], "2");
2843 assert!(req.is_signed());
2844 }
2845
2846 #[tokio::test]
2847 async fn activate_option_posts_empty_body() {
2848 let body = r#"{"code":"0","msg":"","data":[{"result":"true"}]}"#;
2849 let mock = MockTransport::new(body);
2850 let client = signed_client(mock.clone());
2851
2852 let result = client.account().activate_option().await.unwrap();
2853 assert_eq!(result[0].result, "true");
2854
2855 let req = mock.captured();
2856 assert_eq!(req.method, http::Method::POST);
2857 assert!(req.uri.ends_with("/api/v5/account/activate-option"));
2858 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2859 assert_eq!(sent, serde_json::json!({}));
2860 assert!(req.is_signed());
2861 }
2862
2863 #[tokio::test]
2864 async fn position_builder_posts_body_and_omits_unset_fields() {
2865 let body = r#"{"code":"0","msg":"","data":[
2866 {"acctLv":"2","adjEq":"1000","imr":"10","mmr":"5","mr":"100",
2867 "posData":[{"instType":"SWAP","instId":"BTC-USDT-SWAP","pos":"1","avgPx":"42000","upl":"2"}],
2868 "assetData":[{"ccy":"USDT","eq":"1000"}]}]}"#;
2869 let mock = MockTransport::new(body);
2870 let client = signed_client(mock.clone());
2871 let request = super::PositionBuilderRequest::new()
2872 .account_level("2")
2873 .include_real_positions_and_equity(false)
2874 .simulated_positions(vec![
2875 super::SimulatedPosition::new("BTC-USDT-SWAP").position("1"),
2876 ])
2877 .simulated_assets(vec![super::SimulatedAsset::new("USDT").equity("1000")]);
2878
2879 let result = client.account().position_builder(&request).await.unwrap();
2880 assert_eq!(result[0].pos_data[0].inst_id, "BTC-USDT-SWAP");
2881 assert_eq!(result[0].asset_data[0].eq.as_str(), "1000");
2882
2883 let req = mock.captured();
2884 assert_eq!(req.method, http::Method::POST);
2885 assert!(req.uri.ends_with("/api/v5/account/position-builder"));
2886 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2887 assert_eq!(sent["acctLv"], "2");
2888 assert_eq!(sent["inclRealPosAndEq"], false);
2889 assert_eq!(sent["simPos"][0]["instId"], "BTC-USDT-SWAP");
2890 assert_eq!(sent["simAsset"][0]["ccy"], "USDT");
2891 assert!(sent.get("lever").is_none());
2892 assert!(sent["simPos"][0].get("avgPx").is_none());
2893 assert!(req.is_signed());
2894 }
2895}