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(
129 &self,
130 request: &BillsRequest,
131 ) -> Result<Vec<AccountBill>, Error> {
132 self.client.get(BILLS, request, true).await
133 }
134
135 pub async fn get_account_bills_archive(
143 &self,
144 request: &BillsArchiveRequest,
145 ) -> Result<Vec<AccountBill>, Error> {
146 self.client.get(BILLS_ARCHIVE, request, true).await
147 }
148
149 pub async fn set_position_mode(
157 &self,
158 pos_mode: &str,
159 ) -> Result<Vec<SetPositionModeResult>, Error> {
160 let body = SetPositionModeBody { pos_mode };
161 self.client.post(SET_POSITION_MODE, &body, true).await
162 }
163
164 pub async fn set_leverage(
172 &self,
173 request: &SetLeverageRequest,
174 ) -> Result<Vec<LeverageInfo>, Error> {
175 self.client.post(SET_LEVERAGE, request, true).await
176 }
177
178 pub async fn get_leverage(
186 &self,
187 request: &LeverageRequest,
188 ) -> Result<Vec<LeverageInfo>, Error> {
189 self.client.get(GET_LEVERAGE, request, true).await
190 }
191
192 pub async fn get_max_order_size(
200 &self,
201 request: &MaxOrderSizeRequest,
202 ) -> Result<Vec<MaxOrderSize>, Error> {
203 self.client.get(MAX_ORDER_SIZE, request, true).await
204 }
205
206 pub async fn get_max_avail_size(
214 &self,
215 request: &MaxAvailableSizeRequest,
216 ) -> Result<Vec<MaxAvailableSize>, Error> {
217 self.client.get(MAX_AVAILABLE_SIZE, request, true).await
218 }
219
220 pub async fn adjust_margin(
228 &self,
229 request: &AdjustMarginRequest,
230 ) -> Result<Vec<AdjustMarginResult>, Error> {
231 self.client.post(ADJUST_MARGIN, request, true).await
232 }
233
234 pub async fn get_fee_rates(&self, request: &FeeRatesRequest) -> 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(&self, ccy: Option<&str>) -> Result<Vec<InterestRate>, Error> {
292 let query = BalanceQuery { ccy };
293 self.client.get(INTEREST_RATE, &query, true).await
294 }
295
296 pub async fn set_greeks(&self, greeks_type: &str) -> Result<Vec<SetGreeksResult>, Error> {
304 let body = SetGreeksBody { greeks_type };
305 self.client.post(SET_GREEKS, &body, true).await
306 }
307
308 pub async fn set_isolated_mode(
316 &self,
317 iso_mode: &str,
318 mode_type: &str,
319 ) -> Result<Vec<SetIsolatedModeResult>, Error> {
320 let body = SetIsolatedModeBody {
321 iso_mode,
322 mode_type,
323 };
324 self.client.post(SET_ISOLATED_MODE, &body, true).await
325 }
326
327 pub async fn get_max_withdrawal(&self, ccy: Option<&str>) -> Result<Vec<MaxWithdrawal>, Error> {
335 let query = BalanceQuery { ccy };
336 self.client.get(MAX_WITHDRAWAL, &query, true).await
337 }
338
339 pub async fn borrow_repay(
347 &self,
348 request: &BorrowRepayRequest,
349 ) -> Result<Vec<BorrowRepayResult>, Error> {
350 self.client.post(BORROW_REPAY, request, true).await
351 }
352
353 pub async fn get_borrow_repay_history(
361 &self,
362 request: &BorrowRepayHistoryRequest,
363 ) -> Result<Vec<BorrowRepayHistory>, Error> {
364 self.client.get(BORROW_REPAY_HISTORY, request, true).await
365 }
366
367 pub async fn get_interest_limits(
375 &self,
376 request: &InterestLimitsRequest,
377 ) -> Result<Vec<InterestLimit>, Error> {
378 self.client.get(INTEREST_LIMITS, request, true).await
379 }
380
381 pub async fn get_simulated_margin(
391 &self,
392 request: &SimulatedMarginRequest,
393 ) -> Result<Vec<SimulatedMargin>, Error> {
394 self.client.post(SIMULATED_MARGIN, request, true).await
395 }
396
397 pub async fn get_greeks(&self, ccy: Option<&str>) -> Result<Vec<Greek>, Error> {
405 let query = BalanceQuery { ccy };
406 self.client.get(GREEKS, &query, true).await
407 }
408
409 pub async fn get_positions_history(
417 &self,
418 request: &PositionsHistoryRequest,
419 ) -> Result<Vec<PositionHistory>, Error> {
420 self.client.get(POSITIONS_HISTORY, request, true).await
421 }
422
423 pub async fn get_account_position_tiers(
431 &self,
432 request: &AccountPositionTiersRequest,
433 ) -> Result<Vec<AccountPositionTier>, Error> {
434 self.client.get(ACCOUNT_POSITION_TIERS, request, true).await
435 }
436
437 pub async fn get_risk_state(&self) -> Result<Vec<RiskState>, Error> {
445 self.client.get(RISK_STATE, &NoQuery, true).await
446 }
447
448 pub async fn set_risk_offset_type(
456 &self,
457 risk_offset_type: &str,
458 ) -> Result<Vec<SetRiskOffsetTypeResult>, Error> {
459 let body = TypeBody {
460 value: risk_offset_type,
461 };
462 self.client.post(SET_RISK_OFFSET_TYPE, &body, true).await
463 }
464
465 pub async fn set_auto_loan(&self, auto_loan: bool) -> Result<Vec<SetAutoLoanResult>, Error> {
473 let body = SetAutoLoanBody { auto_loan };
474 self.client.post(SET_AUTO_LOAN, &body, true).await
475 }
476
477 pub async fn set_account_level(
485 &self,
486 acct_lv: &str,
487 ) -> Result<Vec<SetAccountLevelResult>, Error> {
488 let body = SetAccountLevelBody { acct_lv };
489 self.client.post(SET_ACCOUNT_LEVEL, &body, true).await
490 }
491
492 pub async fn activate_option(&self) -> Result<Vec<ActivateOptionResult>, Error> {
500 self.client.post(ACTIVATE_OPTION, &EmptyBody {}, true).await
501 }
502
503 pub async fn position_builder(
511 &self,
512 request: &PositionBuilderRequest,
513 ) -> Result<Vec<PositionBuilderResult>, Error> {
514 self.client.post(POSITION_BUILDER, request, true).await
515 }
516}
517
518#[derive(Serialize)]
519struct NoQuery;
520
521#[derive(Serialize)]
522struct EmptyBody {}
523
524#[derive(Serialize)]
525struct BalanceQuery<'a> {
526 #[serde(skip_serializing_if = "Option::is_none")]
527 ccy: Option<&'a str>,
528}
529
530#[derive(Serialize)]
531struct PositionsQuery<'a> {
532 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
533 inst_type: Option<&'a InstType>,
534 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
535 inst_id: Option<&'a str>,
536}
537
538#[derive(Serialize)]
539struct PositionRiskQuery<'a> {
540 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
541 inst_type: Option<&'a InstType>,
542}
543
544#[derive(Serialize)]
545struct SetPositionModeBody<'a> {
546 #[serde(rename = "posMode")]
547 pos_mode: &'a str,
548}
549
550#[derive(Debug, Clone, Default, Serialize)]
552pub struct BillsRequest {
553 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
554 inst_type: Option<InstType>,
555 #[serde(skip_serializing_if = "Option::is_none")]
556 ccy: Option<String>,
557 #[serde(rename = "mgnMode", skip_serializing_if = "Option::is_none")]
558 mgn_mode: Option<TradeMode>,
559 #[serde(rename = "ctType", skip_serializing_if = "Option::is_none")]
560 ct_type: Option<String>,
561 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
562 bill_type: Option<String>,
563 #[serde(rename = "subType", skip_serializing_if = "Option::is_none")]
564 sub_type: Option<String>,
565 #[serde(skip_serializing_if = "Option::is_none")]
566 after: Option<String>,
567 #[serde(skip_serializing_if = "Option::is_none")]
568 before: Option<String>,
569 #[serde(skip_serializing_if = "Option::is_none")]
570 limit: Option<u32>,
571}
572
573impl BillsRequest {
574 pub fn new() -> Self {
576 Self::default()
577 }
578
579 pub fn inst_type(mut self, inst_type: InstType) -> Self {
581 self.inst_type = Some(inst_type);
582 self
583 }
584
585 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
587 self.ccy = Some(ccy.into());
588 self
589 }
590
591 pub fn margin_mode(mut self, mgn_mode: TradeMode) -> Self {
593 self.mgn_mode = Some(mgn_mode);
594 self
595 }
596
597 pub fn contract_type(mut self, ct_type: impl Into<String>) -> Self {
599 self.ct_type = Some(ct_type.into());
600 self
601 }
602
603 pub fn bill_type(mut self, bill_type: impl Into<String>) -> Self {
605 self.bill_type = Some(bill_type.into());
606 self
607 }
608
609 pub fn sub_type(mut self, sub_type: impl Into<String>) -> Self {
611 self.sub_type = Some(sub_type.into());
612 self
613 }
614
615 pub fn after(mut self, after: impl Into<String>) -> Self {
617 self.after = Some(after.into());
618 self
619 }
620
621 pub fn before(mut self, before: impl Into<String>) -> Self {
623 self.before = Some(before.into());
624 self
625 }
626
627 pub fn limit(mut self, limit: u32) -> Self {
629 self.limit = Some(limit);
630 self
631 }
632}
633
634#[derive(Debug, Clone, Default, Serialize)]
636pub struct BillsArchiveRequest {
637 #[serde(flatten)]
638 base: BillsRequest,
639 #[serde(skip_serializing_if = "Option::is_none")]
640 begin: Option<String>,
641 #[serde(skip_serializing_if = "Option::is_none")]
642 end: Option<String>,
643}
644
645impl BillsArchiveRequest {
646 pub fn new() -> Self {
648 Self::default()
649 }
650
651 pub fn filters(mut self, base: BillsRequest) -> Self {
653 self.base = base;
654 self
655 }
656
657 pub fn begin(mut self, begin: impl Into<String>) -> Self {
659 self.begin = Some(begin.into());
660 self
661 }
662
663 pub fn end(mut self, end: impl Into<String>) -> Self {
665 self.end = Some(end.into());
666 self
667 }
668}
669
670#[derive(Debug, Clone, Serialize)]
672pub struct SetLeverageRequest {
673 lever: String,
674 #[serde(rename = "mgnMode")]
675 mgn_mode: TradeMode,
676 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
677 inst_id: Option<String>,
678 #[serde(skip_serializing_if = "Option::is_none")]
679 ccy: Option<String>,
680 #[serde(rename = "posSide", skip_serializing_if = "Option::is_none")]
681 pos_side: Option<PositionSide>,
682}
683
684impl SetLeverageRequest {
685 pub fn new(lever: impl Into<String>, mgn_mode: TradeMode) -> Self {
687 Self {
688 lever: lever.into(),
689 mgn_mode,
690 inst_id: None,
691 ccy: None,
692 pos_side: None,
693 }
694 }
695
696 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
698 self.inst_id = Some(inst_id.into());
699 self
700 }
701
702 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
704 self.ccy = Some(ccy.into());
705 self
706 }
707
708 pub fn position_side(mut self, pos_side: PositionSide) -> Self {
710 self.pos_side = Some(pos_side);
711 self
712 }
713}
714
715#[derive(Debug, Clone, Serialize)]
717pub struct LeverageRequest {
718 #[serde(rename = "mgnMode")]
719 mgn_mode: TradeMode,
720 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
721 inst_id: Option<String>,
722 #[serde(skip_serializing_if = "Option::is_none")]
723 ccy: Option<String>,
724}
725
726impl LeverageRequest {
727 pub fn new(mgn_mode: TradeMode) -> Self {
729 Self {
730 mgn_mode,
731 inst_id: None,
732 ccy: None,
733 }
734 }
735
736 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
738 self.inst_id = Some(inst_id.into());
739 self
740 }
741
742 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
744 self.ccy = Some(ccy.into());
745 self
746 }
747}
748
749#[derive(Debug, Clone, Serialize)]
751pub struct MaxOrderSizeRequest {
752 #[serde(rename = "instId")]
753 inst_id: String,
754 #[serde(rename = "tdMode")]
755 td_mode: TradeMode,
756 #[serde(skip_serializing_if = "Option::is_none")]
757 ccy: Option<String>,
758 #[serde(skip_serializing_if = "Option::is_none")]
759 px: Option<String>,
760 #[serde(rename = "tradeQuoteCcy", skip_serializing_if = "Option::is_none")]
761 trade_quote_ccy: Option<String>,
762}
763
764impl MaxOrderSizeRequest {
765 pub fn new(inst_id: impl Into<String>, td_mode: TradeMode) -> Self {
767 Self {
768 inst_id: inst_id.into(),
769 td_mode,
770 ccy: None,
771 px: None,
772 trade_quote_ccy: None,
773 }
774 }
775
776 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
778 self.ccy = Some(ccy.into());
779 self
780 }
781
782 pub fn price(mut self, px: impl Into<String>) -> Self {
784 self.px = Some(px.into());
785 self
786 }
787
788 pub fn trade_quote_currency(mut self, trade_quote_ccy: impl Into<String>) -> Self {
790 self.trade_quote_ccy = Some(trade_quote_ccy.into());
791 self
792 }
793}
794
795#[derive(Debug, Clone, Serialize)]
797pub struct MaxAvailableSizeRequest {
798 #[serde(rename = "instId")]
799 inst_id: String,
800 #[serde(rename = "tdMode")]
801 td_mode: TradeMode,
802 #[serde(skip_serializing_if = "Option::is_none")]
803 ccy: Option<String>,
804 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
805 reduce_only: Option<bool>,
806 #[serde(rename = "unSpotOffset", skip_serializing_if = "Option::is_none")]
807 un_spot_offset: Option<bool>,
808 #[serde(rename = "quickMgnType", skip_serializing_if = "Option::is_none")]
809 quick_mgn_type: Option<String>,
810 #[serde(rename = "tradeQuoteCcy", skip_serializing_if = "Option::is_none")]
811 trade_quote_ccy: Option<String>,
812}
813
814impl MaxAvailableSizeRequest {
815 pub fn new(inst_id: impl Into<String>, td_mode: TradeMode) -> Self {
817 Self {
818 inst_id: inst_id.into(),
819 td_mode,
820 ccy: None,
821 reduce_only: None,
822 un_spot_offset: None,
823 quick_mgn_type: None,
824 trade_quote_ccy: None,
825 }
826 }
827
828 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
830 self.ccy = Some(ccy.into());
831 self
832 }
833
834 pub fn reduce_only(mut self, reduce_only: bool) -> Self {
836 self.reduce_only = Some(reduce_only);
837 self
838 }
839
840 pub fn un_spot_offset(mut self, un_spot_offset: bool) -> Self {
842 self.un_spot_offset = Some(un_spot_offset);
843 self
844 }
845
846 pub fn quick_margin_type(mut self, quick_mgn_type: impl Into<String>) -> Self {
848 self.quick_mgn_type = Some(quick_mgn_type.into());
849 self
850 }
851
852 pub fn trade_quote_currency(mut self, trade_quote_ccy: impl Into<String>) -> Self {
854 self.trade_quote_ccy = Some(trade_quote_ccy.into());
855 self
856 }
857}
858
859#[derive(Debug, Clone, Serialize)]
861pub struct AdjustMarginRequest {
862 #[serde(rename = "instId")]
863 inst_id: String,
864 #[serde(rename = "posSide")]
865 pos_side: PositionSide,
866 #[serde(rename = "type")]
867 action: String,
868 amt: String,
869 #[serde(rename = "loanTrans", skip_serializing_if = "Option::is_none")]
870 loan_trans: Option<bool>,
871}
872
873impl AdjustMarginRequest {
874 pub fn new(
876 inst_id: impl Into<String>,
877 pos_side: PositionSide,
878 action: impl Into<String>,
879 amt: impl Into<String>,
880 ) -> Self {
881 Self {
882 inst_id: inst_id.into(),
883 pos_side,
884 action: action.into(),
885 amt: amt.into(),
886 loan_trans: None,
887 }
888 }
889
890 pub fn loan_transfer(mut self, loan_trans: bool) -> Self {
892 self.loan_trans = Some(loan_trans);
893 self
894 }
895}
896
897#[derive(Debug, Clone, Serialize)]
899pub struct FeeRatesRequest {
900 #[serde(rename = "instType")]
901 inst_type: InstType,
902 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
903 inst_id: Option<String>,
904 #[serde(rename = "uly", skip_serializing_if = "Option::is_none")]
905 underlying: Option<String>,
906 #[serde(skip_serializing_if = "Option::is_none")]
907 category: Option<String>,
908 #[serde(rename = "instFamily", skip_serializing_if = "Option::is_none")]
909 inst_family: Option<String>,
910}
911
912impl FeeRatesRequest {
913 pub fn new(inst_type: InstType) -> Self {
915 Self {
916 inst_type,
917 inst_id: None,
918 underlying: None,
919 category: None,
920 inst_family: None,
921 }
922 }
923
924 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
926 self.inst_id = Some(inst_id.into());
927 self
928 }
929
930 pub fn underlying(mut self, underlying: impl Into<String>) -> Self {
932 self.underlying = Some(underlying.into());
933 self
934 }
935
936 pub fn category(mut self, category: impl Into<String>) -> Self {
938 self.category = Some(category.into());
939 self
940 }
941
942 pub fn inst_family(mut self, inst_family: impl Into<String>) -> Self {
944 self.inst_family = Some(inst_family.into());
945 self
946 }
947}
948
949#[derive(Debug, Clone, Default, Serialize)]
951pub struct AccountInstrumentsRequest {
952 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
953 inst_type: Option<InstType>,
954 #[serde(rename = "uly", skip_serializing_if = "Option::is_none")]
955 underlying: Option<String>,
956 #[serde(rename = "instFamily", skip_serializing_if = "Option::is_none")]
957 inst_family: Option<String>,
958 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
959 inst_id: Option<String>,
960}
961
962impl AccountInstrumentsRequest {
963 pub fn new() -> Self {
965 Self::default()
966 }
967
968 pub fn inst_type(mut self, inst_type: InstType) -> Self {
970 self.inst_type = Some(inst_type);
971 self
972 }
973
974 pub fn underlying(mut self, underlying: impl Into<String>) -> Self {
976 self.underlying = Some(underlying.into());
977 self
978 }
979
980 pub fn inst_family(mut self, inst_family: impl Into<String>) -> Self {
982 self.inst_family = Some(inst_family.into());
983 self
984 }
985
986 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
988 self.inst_id = Some(inst_id.into());
989 self
990 }
991}
992
993#[derive(Debug, Clone, Serialize)]
995pub struct MaxLoanRequest {
996 #[serde(rename = "instId")]
997 inst_id: String,
998 #[serde(rename = "mgnMode")]
999 mgn_mode: TradeMode,
1000 #[serde(rename = "mgnCcy", skip_serializing_if = "Option::is_none")]
1001 mgn_ccy: Option<String>,
1002 #[serde(rename = "tradeQuoteCcy", skip_serializing_if = "Option::is_none")]
1003 trade_quote_ccy: Option<String>,
1004}
1005
1006impl MaxLoanRequest {
1007 pub fn new(inst_id: impl Into<String>, mgn_mode: TradeMode) -> Self {
1009 Self {
1010 inst_id: inst_id.into(),
1011 mgn_mode,
1012 mgn_ccy: None,
1013 trade_quote_ccy: None,
1014 }
1015 }
1016
1017 pub fn margin_currency(mut self, mgn_ccy: impl Into<String>) -> Self {
1019 self.mgn_ccy = Some(mgn_ccy.into());
1020 self
1021 }
1022
1023 pub fn trade_quote_currency(mut self, trade_quote_ccy: impl Into<String>) -> Self {
1025 self.trade_quote_ccy = Some(trade_quote_ccy.into());
1026 self
1027 }
1028}
1029
1030#[derive(Debug, Clone, Default, Serialize)]
1032pub struct InterestAccruedRequest {
1033 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
1034 inst_id: Option<String>,
1035 #[serde(skip_serializing_if = "Option::is_none")]
1036 ccy: Option<String>,
1037 #[serde(rename = "mgnMode", skip_serializing_if = "Option::is_none")]
1038 mgn_mode: Option<TradeMode>,
1039 #[serde(skip_serializing_if = "Option::is_none")]
1040 after: Option<String>,
1041 #[serde(skip_serializing_if = "Option::is_none")]
1042 before: Option<String>,
1043 #[serde(skip_serializing_if = "Option::is_none")]
1044 limit: Option<u32>,
1045}
1046
1047impl InterestAccruedRequest {
1048 pub fn new() -> Self {
1050 Self::default()
1051 }
1052
1053 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
1055 self.inst_id = Some(inst_id.into());
1056 self
1057 }
1058
1059 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
1061 self.ccy = Some(ccy.into());
1062 self
1063 }
1064
1065 pub fn margin_mode(mut self, mgn_mode: TradeMode) -> Self {
1067 self.mgn_mode = Some(mgn_mode);
1068 self
1069 }
1070
1071 pub fn after(mut self, after: impl Into<String>) -> Self {
1073 self.after = Some(after.into());
1074 self
1075 }
1076
1077 pub fn before(mut self, before: impl Into<String>) -> Self {
1079 self.before = Some(before.into());
1080 self
1081 }
1082
1083 pub fn limit(mut self, limit: u32) -> Self {
1085 self.limit = Some(limit);
1086 self
1087 }
1088}
1089
1090#[derive(Serialize)]
1091struct SetGreeksBody<'a> {
1092 #[serde(rename = "greeksType")]
1093 greeks_type: &'a str,
1094}
1095
1096#[derive(Serialize)]
1097struct SetIsolatedModeBody<'a> {
1098 #[serde(rename = "isoMode")]
1099 iso_mode: &'a str,
1100 #[serde(rename = "type")]
1101 mode_type: &'a str,
1102}
1103
1104#[derive(Debug, Clone, Serialize)]
1106pub struct BorrowRepayRequest {
1107 ccy: String,
1108 side: String,
1109 amt: String,
1110 #[serde(rename = "ordId", skip_serializing_if = "Option::is_none")]
1111 ord_id: Option<String>,
1112}
1113
1114impl BorrowRepayRequest {
1115 pub fn new(ccy: impl Into<String>, side: impl Into<String>, amt: impl Into<String>) -> Self {
1117 Self {
1118 ccy: ccy.into(),
1119 side: side.into(),
1120 amt: amt.into(),
1121 ord_id: None,
1122 }
1123 }
1124
1125 pub fn order_id(mut self, ord_id: impl Into<String>) -> Self {
1127 self.ord_id = Some(ord_id.into());
1128 self
1129 }
1130}
1131
1132#[derive(Debug, Clone, Default, Serialize)]
1134pub struct BorrowRepayHistoryRequest {
1135 #[serde(skip_serializing_if = "Option::is_none")]
1136 ccy: Option<String>,
1137 #[serde(skip_serializing_if = "Option::is_none")]
1138 after: Option<String>,
1139 #[serde(skip_serializing_if = "Option::is_none")]
1140 before: Option<String>,
1141 #[serde(skip_serializing_if = "Option::is_none")]
1142 limit: Option<u32>,
1143}
1144
1145impl BorrowRepayHistoryRequest {
1146 pub fn new() -> Self {
1148 Self::default()
1149 }
1150
1151 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
1153 self.ccy = Some(ccy.into());
1154 self
1155 }
1156
1157 pub fn after(mut self, after: impl Into<String>) -> Self {
1159 self.after = Some(after.into());
1160 self
1161 }
1162
1163 pub fn before(mut self, before: impl Into<String>) -> Self {
1165 self.before = Some(before.into());
1166 self
1167 }
1168
1169 pub fn limit(mut self, limit: u32) -> Self {
1171 self.limit = Some(limit);
1172 self
1173 }
1174}
1175
1176#[derive(Debug, Clone, Default, Serialize)]
1178pub struct InterestLimitsRequest {
1179 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
1180 limit_type: Option<String>,
1181 #[serde(skip_serializing_if = "Option::is_none")]
1182 ccy: Option<String>,
1183}
1184
1185impl InterestLimitsRequest {
1186 pub fn new() -> Self {
1188 Self::default()
1189 }
1190
1191 pub fn limit_type(mut self, limit_type: impl Into<String>) -> Self {
1193 self.limit_type = Some(limit_type.into());
1194 self
1195 }
1196
1197 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
1199 self.ccy = Some(ccy.into());
1200 self
1201 }
1202}
1203
1204#[derive(Debug, Clone, Serialize)]
1206pub struct SimulatedPosition {
1207 #[serde(rename = "instId")]
1208 inst_id: String,
1209 #[serde(skip_serializing_if = "Option::is_none")]
1210 pos: Option<String>,
1211 #[serde(rename = "avgPx", skip_serializing_if = "Option::is_none")]
1212 avg_px: Option<String>,
1213 #[serde(skip_serializing_if = "Option::is_none")]
1214 lever: Option<String>,
1215}
1216
1217impl SimulatedPosition {
1218 pub fn new(inst_id: impl Into<String>) -> Self {
1220 Self {
1221 inst_id: inst_id.into(),
1222 pos: None,
1223 avg_px: None,
1224 lever: None,
1225 }
1226 }
1227
1228 pub fn position(mut self, pos: impl Into<String>) -> Self {
1230 self.pos = Some(pos.into());
1231 self
1232 }
1233
1234 pub fn average_price(mut self, avg_px: impl Into<String>) -> Self {
1236 self.avg_px = Some(avg_px.into());
1237 self
1238 }
1239
1240 pub fn leverage(mut self, lever: impl Into<String>) -> Self {
1242 self.lever = Some(lever.into());
1243 self
1244 }
1245}
1246
1247#[derive(Debug, Clone, Serialize)]
1249pub struct SimulatedAsset {
1250 ccy: String,
1251 #[serde(skip_serializing_if = "Option::is_none")]
1252 eq: Option<String>,
1253}
1254
1255impl SimulatedAsset {
1256 pub fn new(ccy: impl Into<String>) -> Self {
1258 Self {
1259 ccy: ccy.into(),
1260 eq: None,
1261 }
1262 }
1263
1264 pub fn equity(mut self, eq: impl Into<String>) -> Self {
1266 self.eq = Some(eq.into());
1267 self
1268 }
1269}
1270
1271#[derive(Debug, Clone, Default, Serialize)]
1273pub struct SimulatedMarginRequest {
1274 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
1275 inst_type: Option<InstType>,
1276 #[serde(rename = "inclRealPos", skip_serializing_if = "Option::is_none")]
1277 include_real_positions: Option<bool>,
1278 #[serde(rename = "spotOffsetType", skip_serializing_if = "Option::is_none")]
1279 spot_offset_type: Option<String>,
1280 #[serde(rename = "simPos", skip_serializing_if = "Option::is_none")]
1281 simulated_positions: Option<Vec<SimulatedPosition>>,
1282}
1283
1284impl SimulatedMarginRequest {
1285 pub fn new() -> Self {
1287 Self::default()
1288 }
1289
1290 pub fn inst_type(mut self, inst_type: InstType) -> Self {
1292 self.inst_type = Some(inst_type);
1293 self
1294 }
1295
1296 pub fn include_real_positions(mut self, include_real_positions: bool) -> Self {
1298 self.include_real_positions = Some(include_real_positions);
1299 self
1300 }
1301
1302 pub fn spot_offset_type(mut self, spot_offset_type: impl Into<String>) -> Self {
1304 self.spot_offset_type = Some(spot_offset_type.into());
1305 self
1306 }
1307
1308 pub fn simulated_positions(mut self, simulated_positions: Vec<SimulatedPosition>) -> Self {
1310 self.simulated_positions = Some(simulated_positions);
1311 self
1312 }
1313}
1314
1315#[derive(Debug, Clone, Default, Serialize)]
1317pub struct AccountPositionTiersRequest {
1318 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
1319 inst_type: Option<InstType>,
1320 #[serde(rename = "uly", skip_serializing_if = "Option::is_none")]
1321 underlying: Option<String>,
1322 #[serde(rename = "instFamily", skip_serializing_if = "Option::is_none")]
1323 inst_family: Option<String>,
1324}
1325
1326impl AccountPositionTiersRequest {
1327 pub fn new() -> Self {
1329 Self::default()
1330 }
1331
1332 pub fn inst_type(mut self, inst_type: InstType) -> Self {
1334 self.inst_type = Some(inst_type);
1335 self
1336 }
1337
1338 pub fn underlying(mut self, underlying: impl Into<String>) -> Self {
1340 self.underlying = Some(underlying.into());
1341 self
1342 }
1343
1344 pub fn inst_family(mut self, inst_family: impl Into<String>) -> Self {
1346 self.inst_family = Some(inst_family.into());
1347 self
1348 }
1349}
1350
1351#[derive(Serialize)]
1352struct TypeBody<'a> {
1353 #[serde(rename = "type")]
1354 value: &'a str,
1355}
1356
1357#[derive(Serialize)]
1358struct SetAutoLoanBody {
1359 #[serde(rename = "autoLoan")]
1360 auto_loan: bool,
1361}
1362
1363#[derive(Serialize)]
1364struct SetAccountLevelBody<'a> {
1365 #[serde(rename = "acctLv")]
1366 acct_lv: &'a str,
1367}
1368
1369#[derive(Debug, Clone, Default, Serialize)]
1371pub struct PositionBuilderRequest {
1372 #[serde(rename = "acctLv", skip_serializing_if = "Option::is_none")]
1373 acct_lv: Option<String>,
1374 #[serde(rename = "inclRealPosAndEq", skip_serializing_if = "Option::is_none")]
1375 include_real_positions_and_equity: Option<bool>,
1376 #[serde(skip_serializing_if = "Option::is_none")]
1377 lever: Option<String>,
1378 #[serde(rename = "greeksType", skip_serializing_if = "Option::is_none")]
1379 greeks_type: Option<String>,
1380 #[serde(rename = "simPos", skip_serializing_if = "Option::is_none")]
1381 simulated_positions: Option<Vec<SimulatedPosition>>,
1382 #[serde(rename = "simAsset", skip_serializing_if = "Option::is_none")]
1383 simulated_assets: Option<Vec<SimulatedAsset>>,
1384 #[serde(rename = "idxVol", skip_serializing_if = "Option::is_none")]
1385 index_volatility: Option<String>,
1386}
1387
1388impl PositionBuilderRequest {
1389 pub fn new() -> Self {
1391 Self::default()
1392 }
1393
1394 pub fn account_level(mut self, acct_lv: impl Into<String>) -> Self {
1396 self.acct_lv = Some(acct_lv.into());
1397 self
1398 }
1399
1400 pub fn include_real_positions_and_equity(mut self, include: bool) -> Self {
1402 self.include_real_positions_and_equity = Some(include);
1403 self
1404 }
1405
1406 pub fn leverage(mut self, lever: impl Into<String>) -> Self {
1408 self.lever = Some(lever.into());
1409 self
1410 }
1411
1412 pub fn greeks_type(mut self, greeks_type: impl Into<String>) -> Self {
1414 self.greeks_type = Some(greeks_type.into());
1415 self
1416 }
1417
1418 pub fn simulated_positions(mut self, simulated_positions: Vec<SimulatedPosition>) -> Self {
1420 self.simulated_positions = Some(simulated_positions);
1421 self
1422 }
1423
1424 pub fn simulated_assets(mut self, simulated_assets: Vec<SimulatedAsset>) -> Self {
1426 self.simulated_assets = Some(simulated_assets);
1427 self
1428 }
1429
1430 pub fn index_volatility(mut self, index_volatility: impl Into<String>) -> Self {
1432 self.index_volatility = Some(index_volatility.into());
1433 self
1434 }
1435}
1436
1437#[derive(Debug, Clone, Default, Serialize)]
1439pub struct PositionsHistoryRequest {
1440 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
1441 inst_type: Option<InstType>,
1442 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
1443 inst_id: Option<String>,
1444 #[serde(rename = "mgnMode", skip_serializing_if = "Option::is_none")]
1445 mgn_mode: Option<TradeMode>,
1446 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
1447 close_type: Option<String>,
1448 #[serde(rename = "posId", skip_serializing_if = "Option::is_none")]
1449 pos_id: Option<String>,
1450 #[serde(skip_serializing_if = "Option::is_none")]
1451 after: Option<String>,
1452 #[serde(skip_serializing_if = "Option::is_none")]
1453 before: Option<String>,
1454 #[serde(skip_serializing_if = "Option::is_none")]
1455 limit: Option<u32>,
1456}
1457
1458impl PositionsHistoryRequest {
1459 pub fn new() -> Self {
1461 Self::default()
1462 }
1463
1464 pub fn inst_type(mut self, inst_type: InstType) -> Self {
1466 self.inst_type = Some(inst_type);
1467 self
1468 }
1469
1470 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
1472 self.inst_id = Some(inst_id.into());
1473 self
1474 }
1475
1476 pub fn margin_mode(mut self, mgn_mode: TradeMode) -> Self {
1478 self.mgn_mode = Some(mgn_mode);
1479 self
1480 }
1481
1482 pub fn close_type(mut self, close_type: impl Into<String>) -> Self {
1484 self.close_type = Some(close_type.into());
1485 self
1486 }
1487
1488 pub fn position_id(mut self, pos_id: impl Into<String>) -> Self {
1490 self.pos_id = Some(pos_id.into());
1491 self
1492 }
1493
1494 pub fn after(mut self, after: impl Into<String>) -> Self {
1496 self.after = Some(after.into());
1497 self
1498 }
1499
1500 pub fn before(mut self, before: impl Into<String>) -> Self {
1502 self.before = Some(before.into());
1503 self
1504 }
1505
1506 pub fn limit(mut self, limit: u32) -> Self {
1508 self.limit = Some(limit);
1509 self
1510 }
1511}
1512
1513#[derive(Debug, Clone, Deserialize)]
1515#[serde(rename_all = "camelCase")]
1516#[non_exhaustive]
1517pub struct AccountBalance {
1518 #[serde(default)]
1520 pub total_eq: NumberString,
1521 #[serde(default)]
1523 pub adj_eq: NumberString,
1524 #[serde(default)]
1526 pub details: Vec<BalanceDetail>,
1527 #[serde(default)]
1529 pub u_time: NumberString,
1530}
1531
1532#[derive(Debug, Clone, Deserialize)]
1534#[serde(rename_all = "camelCase")]
1535#[non_exhaustive]
1536pub struct BalanceDetail {
1537 pub ccy: String,
1539 #[serde(default)]
1541 pub eq: NumberString,
1542 #[serde(default)]
1544 pub cash_bal: NumberString,
1545 #[serde(default)]
1547 pub avail_bal: NumberString,
1548 #[serde(default)]
1550 pub frozen_bal: NumberString,
1551}
1552
1553#[derive(Debug, Clone, Deserialize)]
1555#[serde(rename_all = "camelCase")]
1556#[non_exhaustive]
1557pub struct Position {
1558 pub inst_type: InstType,
1560 pub inst_id: String,
1562 #[serde(default)]
1564 pub pos_id: String,
1565 pub pos_side: PositionSide,
1567 pub mgn_mode: TradeMode,
1569 #[serde(default)]
1571 pub pos: NumberString,
1572 #[serde(default)]
1574 pub avg_px: NumberString,
1575 #[serde(default)]
1577 pub upl: NumberString,
1578 #[serde(default)]
1580 pub lever: NumberString,
1581 #[serde(default)]
1583 pub liq_px: NumberString,
1584}
1585
1586#[derive(Debug, Clone, Deserialize)]
1588#[serde(rename_all = "camelCase")]
1589#[non_exhaustive]
1590pub struct PositionRisk {
1591 #[serde(default)]
1593 pub adj_eq: NumberString,
1594 #[serde(default)]
1596 pub bal_data: Vec<BalanceDetail>,
1597 #[serde(default)]
1599 pub pos_data: Vec<Position>,
1600 #[serde(default)]
1602 pub ts: NumberString,
1603}
1604
1605#[derive(Debug, Clone, Deserialize)]
1607#[serde(rename_all = "camelCase")]
1608#[non_exhaustive]
1609pub struct AccountConfig {
1610 #[serde(default)]
1612 pub uid: String,
1613 #[serde(default)]
1615 pub acct_lv: String,
1616 #[serde(default)]
1618 pub pos_mode: String,
1619 #[serde(default)]
1621 pub greeks_type: String,
1622 #[serde(default)]
1624 pub auto_loan: bool,
1625}
1626
1627#[derive(Debug, Clone, Deserialize)]
1629#[serde(rename_all = "camelCase")]
1630#[non_exhaustive]
1631pub struct AccountBill {
1632 #[serde(default)]
1634 pub bill_id: String,
1635 #[serde(default)]
1637 pub inst_type: String,
1638 #[serde(default)]
1640 pub inst_id: String,
1641 #[serde(default)]
1643 pub ccy: String,
1644 #[serde(default)]
1646 pub mgn_mode: String,
1647 #[serde(rename = "type", default)]
1649 pub bill_type: String,
1650 #[serde(default)]
1652 pub sub_type: String,
1653 #[serde(default)]
1655 pub sz: NumberString,
1656 #[serde(default)]
1658 pub bal: NumberString,
1659 #[serde(default)]
1661 pub ts: NumberString,
1662}
1663
1664#[derive(Debug, Clone, Deserialize)]
1666#[serde(rename_all = "camelCase")]
1667#[non_exhaustive]
1668pub struct SetPositionModeResult {
1669 #[serde(default)]
1671 pub pos_mode: String,
1672}
1673
1674#[derive(Debug, Clone, Deserialize)]
1676#[serde(rename_all = "camelCase")]
1677#[non_exhaustive]
1678pub struct LeverageInfo {
1679 #[serde(default)]
1681 pub inst_id: String,
1682 pub mgn_mode: TradeMode,
1684 pub pos_side: PositionSide,
1686 #[serde(default)]
1688 pub lever: NumberString,
1689}
1690
1691#[derive(Debug, Clone, Deserialize)]
1693#[serde(rename_all = "camelCase")]
1694#[non_exhaustive]
1695pub struct MaxOrderSize {
1696 pub inst_id: String,
1698 #[serde(default)]
1700 pub max_buy: NumberString,
1701 #[serde(default)]
1703 pub max_sell: NumberString,
1704}
1705
1706#[derive(Debug, Clone, Deserialize)]
1708#[serde(rename_all = "camelCase")]
1709#[non_exhaustive]
1710pub struct MaxAvailableSize {
1711 pub inst_id: String,
1713 #[serde(default)]
1715 pub avail_buy: NumberString,
1716 #[serde(default)]
1718 pub avail_sell: NumberString,
1719}
1720
1721#[derive(Debug, Clone, Deserialize)]
1723#[serde(rename_all = "camelCase")]
1724#[non_exhaustive]
1725pub struct FeeRate {
1726 pub inst_type: InstType,
1728 #[serde(default)]
1730 pub inst_id: String,
1731 #[serde(default)]
1733 pub category: String,
1734 #[serde(default)]
1736 pub maker: NumberString,
1737 #[serde(default)]
1739 pub taker: NumberString,
1740 #[serde(default)]
1742 pub ts: NumberString,
1743}
1744
1745#[derive(Debug, Clone, Deserialize)]
1747#[serde(rename_all = "camelCase")]
1748#[non_exhaustive]
1749pub struct MaxWithdrawal {
1750 pub ccy: String,
1752 #[serde(default)]
1754 pub max_wd: NumberString,
1755 #[serde(default)]
1757 pub max_wd_ex: NumberString,
1758}
1759
1760#[derive(Debug, Clone, Deserialize)]
1762#[serde(rename_all = "camelCase")]
1763#[non_exhaustive]
1764pub struct PositionHistory {
1765 pub inst_type: InstType,
1767 pub inst_id: String,
1769 #[serde(default)]
1771 pub pos_id: String,
1772 pub mgn_mode: TradeMode,
1774 #[serde(rename = "type", default)]
1776 pub close_type: String,
1777 #[serde(default)]
1779 pub realized_pnl: NumberString,
1780 #[serde(default)]
1782 pub c_time: NumberString,
1783 #[serde(default)]
1785 pub u_time: NumberString,
1786}
1787
1788#[derive(Debug, Clone, Deserialize)]
1790#[serde(rename_all = "camelCase")]
1791#[non_exhaustive]
1792pub struct RiskState {
1793 #[serde(default)]
1795 pub at_risk: String,
1796 #[serde(default)]
1798 pub ts: NumberString,
1799}
1800
1801#[derive(Debug, Clone, Deserialize)]
1803#[serde(rename_all = "camelCase")]
1804#[non_exhaustive]
1805pub struct AdjustMarginResult {
1806 #[serde(default)]
1808 pub inst_id: String,
1809 #[serde(default)]
1811 pub pos_side: String,
1812 #[serde(default)]
1814 pub amt: NumberString,
1815 #[serde(rename = "type", default)]
1817 pub adjustment_type: String,
1818}
1819
1820#[derive(Debug, Clone, Deserialize)]
1822#[serde(rename_all = "camelCase")]
1823#[non_exhaustive]
1824pub struct AccountInstrument {
1825 #[serde(default)]
1827 pub inst_type: String,
1828 #[serde(default)]
1830 pub inst_id: String,
1831 #[serde(default)]
1833 pub uly: String,
1834 #[serde(default)]
1836 pub inst_family: String,
1837 #[serde(default)]
1839 pub base_ccy: String,
1840 #[serde(default)]
1842 pub quote_ccy: String,
1843 #[serde(default)]
1845 pub settle_ccy: String,
1846}
1847
1848#[derive(Debug, Clone, Deserialize)]
1850#[serde(rename_all = "camelCase")]
1851#[non_exhaustive]
1852pub struct MaxLoan {
1853 #[serde(default)]
1855 pub inst_id: String,
1856 #[serde(default)]
1858 pub mgn_mode: String,
1859 #[serde(default)]
1861 pub mgn_ccy: String,
1862 #[serde(default)]
1864 pub max_loan: NumberString,
1865}
1866
1867#[derive(Debug, Clone, Deserialize)]
1869#[serde(rename_all = "camelCase")]
1870#[non_exhaustive]
1871pub struct InterestAccrued {
1872 #[serde(default)]
1874 pub inst_id: String,
1875 #[serde(default)]
1877 pub ccy: String,
1878 #[serde(default)]
1880 pub mgn_mode: String,
1881 #[serde(default)]
1883 pub interest: NumberString,
1884 #[serde(default)]
1886 pub interest_rate: NumberString,
1887 #[serde(default)]
1889 pub liab: NumberString,
1890 #[serde(default)]
1892 pub ts: NumberString,
1893}
1894
1895#[derive(Debug, Clone, Deserialize)]
1897#[serde(rename_all = "camelCase")]
1898#[non_exhaustive]
1899pub struct InterestRate {
1900 #[serde(default)]
1902 pub ccy: String,
1903 #[serde(default)]
1905 pub interest_rate: NumberString,
1906}
1907
1908#[derive(Debug, Clone, Deserialize)]
1910#[serde(rename_all = "camelCase")]
1911#[non_exhaustive]
1912pub struct SetGreeksResult {
1913 #[serde(default)]
1915 pub greeks_type: String,
1916}
1917
1918#[derive(Debug, Clone, Deserialize)]
1920#[serde(rename_all = "camelCase")]
1921#[non_exhaustive]
1922pub struct SetIsolatedModeResult {
1923 #[serde(default)]
1925 pub iso_mode: String,
1926 #[serde(rename = "type", default)]
1928 pub mode_type: String,
1929}
1930
1931#[derive(Debug, Clone, Deserialize)]
1933#[serde(rename_all = "camelCase")]
1934#[non_exhaustive]
1935pub struct BorrowRepayResult {
1936 #[serde(default)]
1938 pub ccy: String,
1939 #[serde(default)]
1941 pub side: String,
1942 #[serde(default)]
1944 pub amt: NumberString,
1945 #[serde(default)]
1947 pub ord_id: String,
1948}
1949
1950#[derive(Debug, Clone, Deserialize)]
1952#[serde(rename_all = "camelCase")]
1953#[non_exhaustive]
1954pub struct BorrowRepayHistory {
1955 #[serde(default)]
1957 pub ccy: String,
1958 #[serde(default)]
1960 pub side: String,
1961 #[serde(default)]
1963 pub amt: NumberString,
1964 #[serde(default)]
1966 pub ord_id: String,
1967 #[serde(default)]
1969 pub state: String,
1970 #[serde(default)]
1972 pub ts: NumberString,
1973}
1974
1975#[derive(Debug, Clone, Deserialize)]
1977#[serde(rename_all = "camelCase")]
1978#[non_exhaustive]
1979pub struct InterestLimit {
1980 #[serde(default)]
1982 pub ccy: String,
1983 #[serde(default)]
1985 pub rate: NumberString,
1986 #[serde(default)]
1988 pub loan_quota: NumberString,
1989 #[serde(default)]
1991 pub used_loan: NumberString,
1992}
1993
1994#[derive(Debug, Clone, Deserialize)]
1996#[serde(rename_all = "camelCase")]
1997#[non_exhaustive]
1998pub struct SimulatedMargin {
1999 #[serde(default)]
2001 pub imr: NumberString,
2002 #[serde(default)]
2004 pub mmr: NumberString,
2005 #[serde(default)]
2007 pub mr: NumberString,
2008 #[serde(default)]
2010 pub notional_usd: NumberString,
2011 #[serde(default)]
2013 pub details: Vec<SimulatedMarginDetail>,
2014}
2015
2016#[derive(Debug, Clone, Deserialize)]
2018#[serde(rename_all = "camelCase")]
2019#[non_exhaustive]
2020pub struct SimulatedMarginDetail {
2021 #[serde(default)]
2023 pub inst_id: String,
2024 #[serde(default)]
2026 pub pos: NumberString,
2027 #[serde(default)]
2029 pub imr: NumberString,
2030 #[serde(default)]
2032 pub mmr: NumberString,
2033 #[serde(default)]
2035 pub upl: NumberString,
2036}
2037
2038#[derive(Debug, Clone, Deserialize)]
2040#[serde(rename_all = "camelCase")]
2041#[non_exhaustive]
2042pub struct Greek {
2043 #[serde(default)]
2045 pub ccy: String,
2046 #[serde(rename = "deltaBS", default)]
2048 pub delta_bs: NumberString,
2049 #[serde(rename = "deltaPA", default)]
2051 pub delta_pa: NumberString,
2052 #[serde(rename = "gammaBS", default)]
2054 pub gamma_bs: NumberString,
2055 #[serde(rename = "thetaBS", default)]
2057 pub theta_bs: NumberString,
2058 #[serde(rename = "vegaBS", default)]
2060 pub vega_bs: NumberString,
2061}
2062
2063#[derive(Debug, Clone, Deserialize)]
2065#[serde(rename_all = "camelCase")]
2066#[non_exhaustive]
2067pub struct AccountPositionTier {
2068 #[serde(default)]
2070 pub inst_type: String,
2071 #[serde(default)]
2073 pub uly: String,
2074 #[serde(default)]
2076 pub inst_family: String,
2077 #[serde(default)]
2079 pub pos_type: String,
2080 #[serde(default)]
2082 pub min_sz: NumberString,
2083 #[serde(default)]
2085 pub max_sz: NumberString,
2086}
2087
2088#[derive(Debug, Clone, Deserialize)]
2090#[serde(rename_all = "camelCase")]
2091#[non_exhaustive]
2092pub struct SetRiskOffsetTypeResult {
2093 #[serde(rename = "type", default)]
2095 pub risk_offset_type: String,
2096}
2097
2098#[derive(Debug, Clone, Deserialize)]
2100#[serde(rename_all = "camelCase")]
2101#[non_exhaustive]
2102pub struct SetAutoLoanResult {
2103 #[serde(default)]
2105 pub auto_loan: String,
2106}
2107
2108#[derive(Debug, Clone, Deserialize)]
2110#[serde(rename_all = "camelCase")]
2111#[non_exhaustive]
2112pub struct SetAccountLevelResult {
2113 #[serde(default)]
2115 pub acct_lv: String,
2116}
2117
2118#[derive(Debug, Clone, Deserialize)]
2120#[serde(rename_all = "camelCase")]
2121#[non_exhaustive]
2122pub struct ActivateOptionResult {
2123 #[serde(default)]
2125 pub result: String,
2126}
2127
2128#[derive(Debug, Clone, Deserialize)]
2130#[serde(rename_all = "camelCase")]
2131#[non_exhaustive]
2132pub struct PositionBuilderResult {
2133 #[serde(default)]
2135 pub acct_lv: String,
2136 #[serde(default)]
2138 pub adj_eq: NumberString,
2139 #[serde(default)]
2141 pub imr: NumberString,
2142 #[serde(default)]
2144 pub mmr: NumberString,
2145 #[serde(default)]
2147 pub mr: NumberString,
2148 #[serde(default)]
2150 pub pos_data: Vec<PositionBuilderPosition>,
2151 #[serde(default)]
2153 pub asset_data: Vec<PositionBuilderAsset>,
2154}
2155
2156#[derive(Debug, Clone, Deserialize)]
2158#[serde(rename_all = "camelCase")]
2159#[non_exhaustive]
2160pub struct PositionBuilderPosition {
2161 #[serde(default)]
2163 pub inst_type: String,
2164 #[serde(default)]
2166 pub inst_id: String,
2167 #[serde(default)]
2169 pub pos: NumberString,
2170 #[serde(default)]
2172 pub avg_px: NumberString,
2173 #[serde(default)]
2175 pub upl: NumberString,
2176}
2177
2178#[derive(Debug, Clone, Deserialize)]
2180#[serde(rename_all = "camelCase")]
2181#[non_exhaustive]
2182pub struct PositionBuilderAsset {
2183 #[serde(default)]
2185 pub ccy: String,
2186 #[serde(default)]
2188 pub eq: NumberString,
2189}
2190
2191#[cfg(test)]
2192mod tests {
2193 use crate::test_util::MockTransport;
2194 use crate::{Credentials, OkxClient};
2195
2196 fn signed_client(mock: MockTransport) -> OkxClient<MockTransport> {
2197 OkxClient::with_transport(mock)
2198 .credentials(Credentials::new("key", "secret", "pass"))
2199 .build()
2200 }
2201
2202 #[tokio::test]
2203 async fn get_balance_signs_request_and_parses() {
2204 let body = r#"{"code":"0","msg":"","data":[
2205 {"totalEq":"10000","adjEq":"9500","uTime":"1597026383085","details":[
2206 {"ccy":"USDT","eq":"10000","cashBal":"10000","availBal":"9000","frozenBal":"1000"}]}]}"#;
2207 let mock = MockTransport::new(body);
2208 let client = signed_client(mock.clone());
2209
2210 let balances = client.account().get_balance(None).await.unwrap();
2211 assert_eq!(balances[0].total_eq.as_str(), "10000");
2212 assert_eq!(balances[0].details[0].ccy, "USDT");
2213 assert_eq!(balances[0].details[0].avail_bal.as_str(), "9000");
2214
2215 let req = mock.captured();
2216 assert_eq!(req.method, http::Method::GET);
2217 assert!(req.uri.ends_with("/api/v5/account/balance"));
2218 assert!(req.is_signed(), "authenticated endpoint must be signed");
2219 }
2220
2221 #[tokio::test]
2222 async fn get_positions_passes_filters() {
2223 let body = r#"{"code":"0","msg":"","data":[
2224 {"instType":"SWAP","instId":"BTC-USDT-SWAP","posId":"1","posSide":"long",
2225 "mgnMode":"cross","pos":"1","avgPx":"42000","upl":"5","lever":"10","liqPx":"38000"}]}"#;
2226 let mock = MockTransport::new(body);
2227 let client = signed_client(mock.clone());
2228
2229 let positions = client
2230 .account()
2231 .get_positions(Some(crate::model::InstType::Swap), Some("BTC-USDT-SWAP"))
2232 .await
2233 .unwrap();
2234 assert_eq!(positions[0].inst_id, "BTC-USDT-SWAP");
2235 assert_eq!(positions[0].pos_side, crate::model::PositionSide::Long);
2236
2237 let req = mock.captured();
2238 assert_eq!(req.query(), Some("instType=SWAP&instId=BTC-USDT-SWAP"));
2239 assert!(req.is_signed());
2240 }
2241
2242 #[tokio::test]
2243 async fn missing_credentials_is_configuration_error() {
2244 let mock = MockTransport::new("{}");
2245 let client = OkxClient::with_transport(mock).build();
2246 let err = client.account().get_balance(None).await.unwrap_err();
2247 assert!(matches!(err, crate::Error::Configuration(_)));
2248 }
2249
2250 #[tokio::test]
2251 async fn get_position_risk_signs_and_parses() {
2252 let body = r#"{"code":"0","msg":"","data":[
2253 {"adjEq":"1000","ts":"1597026383085","balData":[{"ccy":"USDT","eq":"1000"}],
2254 "posData":[{"instType":"SWAP","instId":"BTC-USDT-SWAP","posSide":"long","mgnMode":"cross","pos":"1"}]}]}"#;
2255 let mock = MockTransport::new(body);
2256 let client = signed_client(mock.clone());
2257
2258 let risk = client
2259 .account()
2260 .get_position_risk(Some(crate::model::InstType::Swap))
2261 .await
2262 .unwrap();
2263 assert_eq!(risk[0].adj_eq.as_str(), "1000");
2264 assert_eq!(risk[0].pos_data[0].inst_id, "BTC-USDT-SWAP");
2265
2266 let req = mock.captured();
2267 assert_eq!(req.query(), Some("instType=SWAP"));
2268 assert!(req.is_signed());
2269 }
2270
2271 #[tokio::test]
2272 async fn get_account_config_signs_and_parses() {
2273 let body = r#"{"code":"0","msg":"","data":[
2274 {"uid":"1","acctLv":"2","posMode":"net_mode","greeksType":"PA","autoLoan":false}]}"#;
2275 let mock = MockTransport::new(body);
2276 let client = signed_client(mock.clone());
2277
2278 let config = client.account().get_account_config().await.unwrap();
2279 assert_eq!(config[0].pos_mode, "net_mode");
2280
2281 let req = mock.captured();
2282 assert!(req.uri.ends_with("/api/v5/account/config"));
2283 assert_eq!(req.query(), None);
2284 assert!(req.is_signed());
2285 }
2286
2287 #[tokio::test]
2288 async fn get_account_bills_uses_builder_query() {
2289 let body = r#"{"code":"0","msg":"","data":[
2290 {"billId":"1","instType":"SPOT","ccy":"USDT","mgnMode":"cash","type":"1","subType":"2",
2291 "sz":"10","bal":"100","ts":"1597026383085"}]}"#;
2292 let mock = MockTransport::new(body);
2293 let client = signed_client(mock.clone());
2294 let request = super::BillsRequest::new()
2295 .inst_type(crate::model::InstType::Spot)
2296 .currency("USDT")
2297 .bill_type("1")
2298 .limit(1);
2299
2300 let bills = client.account().get_account_bills(&request).await.unwrap();
2301 assert_eq!(bills[0].bill_id, "1");
2302
2303 let req = mock.captured();
2304 assert_eq!(req.query(), Some("instType=SPOT&ccy=USDT&type=1&limit=1"));
2305 assert!(req.is_signed());
2306 }
2307
2308 #[tokio::test]
2309 async fn get_account_bills_archive_uses_builder_query() {
2310 let body = r#"{"code":"0","msg":"","data":[
2311 {"billId":"2","instType":"SPOT","ccy":"USDT","type":"1","sz":"10","bal":"100","ts":"1597026383085"}]}"#;
2312 let mock = MockTransport::new(body);
2313 let client = signed_client(mock.clone());
2314 let request = super::BillsArchiveRequest::new()
2315 .filters(super::BillsRequest::new().currency("USDT"))
2316 .begin("100")
2317 .end("200");
2318
2319 let bills = client
2320 .account()
2321 .get_account_bills_archive(&request)
2322 .await
2323 .unwrap();
2324 assert_eq!(bills[0].bill_id, "2");
2325
2326 let req = mock.captured();
2327 assert_eq!(req.query(), Some("ccy=USDT&begin=100&end=200"));
2328 assert!(req.is_signed());
2329 }
2330
2331 #[tokio::test]
2332 async fn set_position_mode_posts_body() {
2333 let body = r#"{"code":"0","msg":"","data":[{"posMode":"net_mode"}]}"#;
2334 let mock = MockTransport::new(body);
2335 let client = signed_client(mock.clone());
2336
2337 let result = client
2338 .account()
2339 .set_position_mode("net_mode")
2340 .await
2341 .unwrap();
2342 assert_eq!(result[0].pos_mode, "net_mode");
2343
2344 let req = mock.captured();
2345 assert_eq!(req.method, http::Method::POST);
2346 assert!(req.uri.ends_with("/api/v5/account/set-position-mode"));
2347 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2348 assert_eq!(sent["posMode"], "net_mode");
2349 assert!(req.is_signed());
2350 }
2351
2352 #[tokio::test]
2353 async fn set_leverage_posts_builder_body() {
2354 let body = r#"{"code":"0","msg":"","data":[
2355 {"instId":"BTC-USDT-SWAP","mgnMode":"cross","posSide":"long","lever":"10"}]}"#;
2356 let mock = MockTransport::new(body);
2357 let client = signed_client(mock.clone());
2358 let request = super::SetLeverageRequest::new("10", crate::model::TradeMode::Cross)
2359 .inst_id("BTC-USDT-SWAP")
2360 .position_side(crate::model::PositionSide::Long);
2361
2362 let result = client.account().set_leverage(&request).await.unwrap();
2363 assert_eq!(result[0].lever.as_str(), "10");
2364
2365 let req = mock.captured();
2366 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2367 assert_eq!(sent["lever"], "10");
2368 assert_eq!(sent["mgnMode"], "cross");
2369 assert_eq!(sent["instId"], "BTC-USDT-SWAP");
2370 assert_eq!(sent["posSide"], "long");
2371 assert!(req.is_signed());
2372 }
2373
2374 #[tokio::test]
2375 async fn get_leverage_uses_builder_query() {
2376 let body = r#"{"code":"0","msg":"","data":[
2377 {"instId":"BTC-USDT-SWAP","mgnMode":"cross","posSide":"long","lever":"10"}]}"#;
2378 let mock = MockTransport::new(body);
2379 let client = signed_client(mock.clone());
2380 let request =
2381 super::LeverageRequest::new(crate::model::TradeMode::Cross).inst_id("BTC-USDT-SWAP");
2382
2383 let result = client.account().get_leverage(&request).await.unwrap();
2384 assert_eq!(result[0].mgn_mode, crate::model::TradeMode::Cross);
2385
2386 let req = mock.captured();
2387 assert_eq!(req.query(), Some("mgnMode=cross&instId=BTC-USDT-SWAP"));
2388 assert!(req.is_signed());
2389 }
2390
2391 #[tokio::test]
2392 async fn get_max_order_size_uses_builder_query() {
2393 let body = r#"{"code":"0","msg":"","data":[
2394 {"instId":"BTC-USDT","maxBuy":"1","maxSell":"2"}]}"#;
2395 let mock = MockTransport::new(body);
2396 let client = signed_client(mock.clone());
2397 let request = super::MaxOrderSizeRequest::new("BTC-USDT", crate::model::TradeMode::Cash)
2398 .price("42000");
2399
2400 let result = client.account().get_max_order_size(&request).await.unwrap();
2401 assert_eq!(result[0].max_buy.as_str(), "1");
2402
2403 let req = mock.captured();
2404 assert_eq!(req.query(), Some("instId=BTC-USDT&tdMode=cash&px=42000"));
2405 assert!(req.is_signed());
2406 }
2407
2408 #[tokio::test]
2409 async fn get_max_avail_size_uses_builder_query() {
2410 let body = r#"{"code":"0","msg":"","data":[
2411 {"instId":"BTC-USDT","availBuy":"1","availSell":"2"}]}"#;
2412 let mock = MockTransport::new(body);
2413 let client = signed_client(mock.clone());
2414 let request =
2415 super::MaxAvailableSizeRequest::new("BTC-USDT", crate::model::TradeMode::Cash)
2416 .reduce_only(false);
2417
2418 let result = client.account().get_max_avail_size(&request).await.unwrap();
2419 assert_eq!(result[0].avail_sell.as_str(), "2");
2420
2421 let req = mock.captured();
2422 assert_eq!(
2423 req.query(),
2424 Some("instId=BTC-USDT&tdMode=cash&reduceOnly=false")
2425 );
2426 assert!(req.is_signed());
2427 }
2428
2429 #[tokio::test]
2430 async fn get_fee_rates_uses_builder_query() {
2431 let body = r#"{"code":"0","msg":"","data":[
2432 {"instType":"SPOT","instId":"BTC-USDT","category":"1","maker":"-0.0001","taker":"0.001","ts":"1597026383085"}]}"#;
2433 let mock = MockTransport::new(body);
2434 let client = signed_client(mock.clone());
2435 let request = super::FeeRatesRequest::new(crate::model::InstType::Spot).inst_id("BTC-USDT");
2436
2437 let result = client.account().get_fee_rates(&request).await.unwrap();
2438 assert_eq!(result[0].maker.as_str(), "-0.0001");
2439
2440 let req = mock.captured();
2441 assert_eq!(req.query(), Some("instType=SPOT&instId=BTC-USDT"));
2442 assert!(req.is_signed());
2443 }
2444
2445 #[tokio::test]
2446 async fn get_max_withdrawal_queries_currency() {
2447 let body = r#"{"code":"0","msg":"","data":[
2448 {"ccy":"USDT","maxWd":"100","maxWdEx":"90"}]}"#;
2449 let mock = MockTransport::new(body);
2450 let client = signed_client(mock.clone());
2451
2452 let result = client
2453 .account()
2454 .get_max_withdrawal(Some("USDT"))
2455 .await
2456 .unwrap();
2457 assert_eq!(result[0].max_wd.as_str(), "100");
2458
2459 let req = mock.captured();
2460 assert_eq!(req.query(), Some("ccy=USDT"));
2461 assert!(req.is_signed());
2462 }
2463
2464 #[tokio::test]
2465 async fn get_positions_history_uses_builder_query() {
2466 let body = r#"{"code":"0","msg":"","data":[
2467 {"instType":"SWAP","instId":"BTC-USDT-SWAP","posId":"1","mgnMode":"cross",
2468 "type":"2","realizedPnl":"5","cTime":"1597026383085","uTime":"1597026383999"}]}"#;
2469 let mock = MockTransport::new(body);
2470 let client = signed_client(mock.clone());
2471 let request = super::PositionsHistoryRequest::new()
2472 .inst_type(crate::model::InstType::Swap)
2473 .inst_id("BTC-USDT-SWAP")
2474 .limit(1);
2475
2476 let result = client
2477 .account()
2478 .get_positions_history(&request)
2479 .await
2480 .unwrap();
2481 assert_eq!(result[0].realized_pnl.as_str(), "5");
2482
2483 let req = mock.captured();
2484 assert_eq!(
2485 req.query(),
2486 Some("instType=SWAP&instId=BTC-USDT-SWAP&limit=1")
2487 );
2488 assert!(req.is_signed());
2489 }
2490
2491 #[tokio::test]
2492 async fn get_risk_state_signs_and_parses() {
2493 let body = r#"{"code":"0","msg":"","data":[{"atRisk":"false","ts":"1597026383085"}]}"#;
2494 let mock = MockTransport::new(body);
2495 let client = signed_client(mock.clone());
2496
2497 let result = client.account().get_risk_state().await.unwrap();
2498 assert_eq!(result[0].at_risk, "false");
2499
2500 let req = mock.captured();
2501 assert!(req.uri.ends_with("/api/v5/account/risk-state"));
2502 assert_eq!(req.query(), None);
2503 assert!(req.is_signed());
2504 }
2505
2506 #[tokio::test]
2507 async fn demo_trading_sets_simulated_header() {
2508 let body = r#"{"code":"0","msg":"","data":[
2509 {"uid":"1","acctLv":"2","posMode":"net_mode","greeksType":"PA","autoLoan":false}]}"#;
2510 let mock = MockTransport::new(body);
2511 let client = OkxClient::with_transport(mock.clone())
2512 .credentials(Credentials::new("key", "secret", "pass"))
2513 .demo_trading(true)
2514 .build();
2515
2516 let config = client.account().get_account_config().await.unwrap();
2517 assert_eq!(config[0].acct_lv, "2");
2518
2519 let req = mock.captured();
2520 assert_eq!(
2521 req.headers
2522 .get("x-simulated-trading")
2523 .and_then(|v| v.to_str().ok()),
2524 Some("1")
2525 );
2526 assert!(req.is_signed());
2527 }
2528
2529 #[tokio::test]
2530 async fn adjust_margin_posts_body() {
2531 let body = r#"{"code":"0","msg":"","data":[
2532 {"instId":"BTC-USDT-SWAP","posSide":"long","type":"add","amt":"100"}]}"#;
2533 let mock = MockTransport::new(body);
2534 let client = signed_client(mock.clone());
2535 let request = super::AdjustMarginRequest::new(
2536 "BTC-USDT-SWAP",
2537 crate::model::PositionSide::Long,
2538 "add",
2539 "100",
2540 )
2541 .loan_transfer(true);
2542
2543 let result = client.account().adjust_margin(&request).await.unwrap();
2544 assert_eq!(result[0].amt.as_str(), "100");
2545
2546 let req = mock.captured();
2547 assert_eq!(req.method, http::Method::POST);
2548 assert!(req.uri.ends_with("/api/v5/account/position/margin-balance"));
2549 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2550 assert_eq!(sent["instId"], "BTC-USDT-SWAP");
2551 assert_eq!(sent["posSide"], "long");
2552 assert_eq!(sent["type"], "add");
2553 assert_eq!(sent["amt"], "100");
2554 assert_eq!(sent["loanTrans"], true);
2555 assert!(req.is_signed());
2556 }
2557
2558 #[tokio::test]
2559 async fn get_account_instruments_uses_builder_query() {
2560 let body = r#"{"code":"0","msg":"","data":[
2561 {"instType":"SWAP","instId":"BTC-USDT-SWAP","uly":"BTC-USDT","instFamily":"BTC-USDT",
2562 "baseCcy":"BTC","quoteCcy":"USDT","settleCcy":"USDT"}]}"#;
2563 let mock = MockTransport::new(body);
2564 let client = signed_client(mock.clone());
2565 let request = super::AccountInstrumentsRequest::new()
2566 .inst_type(crate::model::InstType::Swap)
2567 .inst_id("BTC-USDT-SWAP");
2568
2569 let result = client
2570 .account()
2571 .get_account_instruments(&request)
2572 .await
2573 .unwrap();
2574 assert_eq!(result[0].settle_ccy, "USDT");
2575
2576 let req = mock.captured();
2577 assert_eq!(req.query(), Some("instType=SWAP&instId=BTC-USDT-SWAP"));
2578 assert!(req.is_signed());
2579 }
2580
2581 #[tokio::test]
2582 async fn get_max_loan_uses_builder_query() {
2583 let body = r#"{"code":"0","msg":"","data":[
2584 {"instId":"BTC-USDT","mgnMode":"cross","mgnCcy":"USDT","maxLoan":"1000"}]}"#;
2585 let mock = MockTransport::new(body);
2586 let client = signed_client(mock.clone());
2587 let request = super::MaxLoanRequest::new("BTC-USDT", crate::model::TradeMode::Cross)
2588 .margin_currency("USDT");
2589
2590 let result = client.account().get_max_loan(&request).await.unwrap();
2591 assert_eq!(result[0].max_loan.as_str(), "1000");
2592
2593 let req = mock.captured();
2594 assert_eq!(
2595 req.query(),
2596 Some("instId=BTC-USDT&mgnMode=cross&mgnCcy=USDT")
2597 );
2598 assert!(req.is_signed());
2599 }
2600
2601 #[tokio::test]
2602 async fn get_interest_accrued_uses_builder_query() {
2603 let body = r#"{"code":"0","msg":"","data":[
2604 {"instId":"BTC-USDT","ccy":"USDT","mgnMode":"cross","interest":"1",
2605 "interestRate":"0.0001","liab":"100","ts":"1597026383085"}]}"#;
2606 let mock = MockTransport::new(body);
2607 let client = signed_client(mock.clone());
2608 let request = super::InterestAccruedRequest::new()
2609 .inst_id("BTC-USDT")
2610 .currency("USDT")
2611 .limit(1);
2612
2613 let result = client
2614 .account()
2615 .get_interest_accrued(&request)
2616 .await
2617 .unwrap();
2618 assert_eq!(result[0].interest_rate.as_str(), "0.0001");
2619
2620 let req = mock.captured();
2621 assert_eq!(req.query(), Some("instId=BTC-USDT&ccy=USDT&limit=1"));
2622 assert!(req.is_signed());
2623 }
2624
2625 #[tokio::test]
2626 async fn get_interest_rate_queries_currency() {
2627 let body = r#"{"code":"0","msg":"","data":[{"ccy":"USDT","interestRate":"0.0001"}]}"#;
2628 let mock = MockTransport::new(body);
2629 let client = signed_client(mock.clone());
2630
2631 let result = client
2632 .account()
2633 .get_interest_rate(Some("USDT"))
2634 .await
2635 .unwrap();
2636 assert_eq!(result[0].ccy, "USDT");
2637
2638 let req = mock.captured();
2639 assert_eq!(req.query(), Some("ccy=USDT"));
2640 assert!(req.is_signed());
2641 }
2642
2643 #[tokio::test]
2644 async fn set_greeks_posts_body() {
2645 let body = r#"{"code":"0","msg":"","data":[{"greeksType":"PA"}]}"#;
2646 let mock = MockTransport::new(body);
2647 let client = signed_client(mock.clone());
2648
2649 let result = client.account().set_greeks("PA").await.unwrap();
2650 assert_eq!(result[0].greeks_type, "PA");
2651
2652 let req = mock.captured();
2653 assert_eq!(req.method, http::Method::POST);
2654 assert!(req.uri.ends_with("/api/v5/account/set-greeks"));
2655 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2656 assert_eq!(sent["greeksType"], "PA");
2657 assert!(req.is_signed());
2658 }
2659
2660 #[tokio::test]
2661 async fn set_isolated_mode_posts_body() {
2662 let body = r#"{"code":"0","msg":"","data":[{"isoMode":"automatic","type":"MARGIN"}]}"#;
2663 let mock = MockTransport::new(body);
2664 let client = signed_client(mock.clone());
2665
2666 let result = client
2667 .account()
2668 .set_isolated_mode("automatic", "MARGIN")
2669 .await
2670 .unwrap();
2671 assert_eq!(result[0].iso_mode, "automatic");
2672
2673 let req = mock.captured();
2674 assert_eq!(req.method, http::Method::POST);
2675 assert!(req.uri.ends_with("/api/v5/account/set-isolated-mode"));
2676 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2677 assert_eq!(sent["isoMode"], "automatic");
2678 assert_eq!(sent["type"], "MARGIN");
2679 assert!(req.is_signed());
2680 }
2681
2682 #[tokio::test]
2683 async fn borrow_repay_posts_body() {
2684 let body = r#"{"code":"0","msg":"","data":[
2685 {"ccy":"USDT","side":"borrow","amt":"100","ordId":"1"}]}"#;
2686 let mock = MockTransport::new(body);
2687 let client = signed_client(mock.clone());
2688 let request = super::BorrowRepayRequest::new("USDT", "borrow", "100").order_id("1");
2689
2690 let result = client.account().borrow_repay(&request).await.unwrap();
2691 assert_eq!(result[0].ord_id, "1");
2692
2693 let req = mock.captured();
2694 assert_eq!(req.method, http::Method::POST);
2695 assert!(req.uri.ends_with("/api/v5/account/borrow-repay"));
2696 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2697 assert_eq!(sent["ccy"], "USDT");
2698 assert_eq!(sent["side"], "borrow");
2699 assert_eq!(sent["amt"], "100");
2700 assert_eq!(sent["ordId"], "1");
2701 assert!(req.is_signed());
2702 }
2703
2704 #[tokio::test]
2705 async fn get_borrow_repay_history_uses_builder_query() {
2706 let body = r#"{"code":"0","msg":"","data":[
2707 {"ccy":"USDT","side":"borrow","amt":"100","ordId":"1","state":"2","ts":"1597026383085"}]}"#;
2708 let mock = MockTransport::new(body);
2709 let client = signed_client(mock.clone());
2710 let request = super::BorrowRepayHistoryRequest::new()
2711 .currency("USDT")
2712 .limit(1);
2713
2714 let result = client
2715 .account()
2716 .get_borrow_repay_history(&request)
2717 .await
2718 .unwrap();
2719 assert_eq!(result[0].state, "2");
2720
2721 let req = mock.captured();
2722 assert_eq!(req.query(), Some("ccy=USDT&limit=1"));
2723 assert!(req.is_signed());
2724 }
2725
2726 #[tokio::test]
2727 async fn get_interest_limits_uses_builder_query() {
2728 let body = r#"{"code":"0","msg":"","data":[
2729 {"ccy":"USDT","rate":"0.0001","loanQuota":"1000","usedLoan":"100"}]}"#;
2730 let mock = MockTransport::new(body);
2731 let client = signed_client(mock.clone());
2732 let request = super::InterestLimitsRequest::new()
2733 .limit_type("1")
2734 .currency("USDT");
2735
2736 let result = client
2737 .account()
2738 .get_interest_limits(&request)
2739 .await
2740 .unwrap();
2741 assert_eq!(result[0].loan_quota.as_str(), "1000");
2742
2743 let req = mock.captured();
2744 assert_eq!(req.query(), Some("type=1&ccy=USDT"));
2745 assert!(req.is_signed());
2746 }
2747
2748 #[tokio::test]
2749 async fn get_simulated_margin_posts_body_and_omits_unset_fields() {
2750 let body = r#"{"code":"0","msg":"","data":[
2751 {"imr":"10","mmr":"5","mr":"100","notionalUsd":"1000",
2752 "details":[{"instId":"BTC-USDT-SWAP","pos":"1","imr":"10","mmr":"5","upl":"2"}]}]}"#;
2753 let mock = MockTransport::new(body);
2754 let client = signed_client(mock.clone());
2755 let request = super::SimulatedMarginRequest::new()
2756 .inst_type(crate::model::InstType::Swap)
2757 .simulated_positions(vec![
2758 super::SimulatedPosition::new("BTC-USDT-SWAP")
2759 .position("1")
2760 .leverage("10"),
2761 ]);
2762
2763 let result = client
2764 .account()
2765 .get_simulated_margin(&request)
2766 .await
2767 .unwrap();
2768 assert_eq!(result[0].details[0].upl.as_str(), "2");
2769
2770 let req = mock.captured();
2771 assert_eq!(req.method, http::Method::POST);
2772 assert!(req.uri.ends_with("/api/v5/account/simulated_margin"));
2773 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2774 assert_eq!(sent["instType"], "SWAP");
2775 assert_eq!(sent["simPos"][0]["instId"], "BTC-USDT-SWAP");
2776 assert_eq!(sent["simPos"][0]["pos"], "1");
2777 assert_eq!(sent["simPos"][0]["lever"], "10");
2778 assert!(sent.get("inclRealPos").is_none());
2779 assert!(sent["simPos"][0].get("avgPx").is_none());
2780 assert!(req.is_signed());
2781 }
2782
2783 #[tokio::test]
2784 async fn get_greeks_queries_currency() {
2785 let body = r#"{"code":"0","msg":"","data":[
2786 {"ccy":"BTC","deltaBS":"1","deltaPA":"0.9","gammaBS":"0.1","thetaBS":"-0.01","vegaBS":"2"}]}"#;
2787 let mock = MockTransport::new(body);
2788 let client = signed_client(mock.clone());
2789
2790 let result = client.account().get_greeks(Some("BTC")).await.unwrap();
2791 assert_eq!(result[0].delta_pa.as_str(), "0.9");
2792
2793 let req = mock.captured();
2794 assert_eq!(req.query(), Some("ccy=BTC"));
2795 assert!(req.is_signed());
2796 }
2797
2798 #[tokio::test]
2799 async fn get_account_position_tiers_uses_builder_query() {
2800 let body = r#"{"code":"0","msg":"","data":[
2801 {"instType":"OPTION","uly":"BTC-USD","instFamily":"BTC-USD","posType":"1","minSz":"0","maxSz":"100"}]}"#;
2802 let mock = MockTransport::new(body);
2803 let client = signed_client(mock.clone());
2804 let request = super::AccountPositionTiersRequest::new()
2805 .inst_type(crate::model::InstType::Option)
2806 .underlying("BTC-USD");
2807
2808 let result = client
2809 .account()
2810 .get_account_position_tiers(&request)
2811 .await
2812 .unwrap();
2813 assert_eq!(result[0].max_sz.as_str(), "100");
2814
2815 let req = mock.captured();
2816 assert_eq!(req.query(), Some("instType=OPTION&uly=BTC-USD"));
2817 assert!(req.is_signed());
2818 }
2819
2820 #[tokio::test]
2821 async fn set_risk_offset_type_posts_body() {
2822 let body = r#"{"code":"0","msg":"","data":[{"type":"1"}]}"#;
2823 let mock = MockTransport::new(body);
2824 let client = signed_client(mock.clone());
2825
2826 let result = client.account().set_risk_offset_type("1").await.unwrap();
2827 assert_eq!(result[0].risk_offset_type, "1");
2828
2829 let req = mock.captured();
2830 assert_eq!(req.method, http::Method::POST);
2831 assert!(req.uri.ends_with("/api/v5/account/set-riskOffset-type"));
2832 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2833 assert_eq!(sent["type"], "1");
2834 assert!(req.is_signed());
2835 }
2836
2837 #[tokio::test]
2838 async fn set_auto_loan_posts_body() {
2839 let body = r#"{"code":"0","msg":"","data":[{"autoLoan":"true"}]}"#;
2840 let mock = MockTransport::new(body);
2841 let client = signed_client(mock.clone());
2842
2843 let result = client.account().set_auto_loan(true).await.unwrap();
2844 assert_eq!(result[0].auto_loan, "true");
2845
2846 let req = mock.captured();
2847 assert_eq!(req.method, http::Method::POST);
2848 assert!(req.uri.ends_with("/api/v5/account/set-auto-loan"));
2849 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2850 assert_eq!(sent["autoLoan"], true);
2851 assert!(req.is_signed());
2852 }
2853
2854 #[tokio::test]
2855 async fn set_account_level_posts_body() {
2856 let body = r#"{"code":"0","msg":"","data":[{"acctLv":"2"}]}"#;
2857 let mock = MockTransport::new(body);
2858 let client = signed_client(mock.clone());
2859
2860 let result = client.account().set_account_level("2").await.unwrap();
2861 assert_eq!(result[0].acct_lv, "2");
2862
2863 let req = mock.captured();
2864 assert_eq!(req.method, http::Method::POST);
2865 assert!(req.uri.ends_with("/api/v5/account/set-account-level"));
2866 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2867 assert_eq!(sent["acctLv"], "2");
2868 assert!(req.is_signed());
2869 }
2870
2871 #[tokio::test]
2872 async fn activate_option_posts_empty_body() {
2873 let body = r#"{"code":"0","msg":"","data":[{"result":"true"}]}"#;
2874 let mock = MockTransport::new(body);
2875 let client = signed_client(mock.clone());
2876
2877 let result = client.account().activate_option().await.unwrap();
2878 assert_eq!(result[0].result, "true");
2879
2880 let req = mock.captured();
2881 assert_eq!(req.method, http::Method::POST);
2882 assert!(req.uri.ends_with("/api/v5/account/activate-option"));
2883 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2884 assert_eq!(sent, serde_json::json!({}));
2885 assert!(req.is_signed());
2886 }
2887
2888 #[tokio::test]
2889 async fn position_builder_posts_body_and_omits_unset_fields() {
2890 let body = r#"{"code":"0","msg":"","data":[
2891 {"acctLv":"2","adjEq":"1000","imr":"10","mmr":"5","mr":"100",
2892 "posData":[{"instType":"SWAP","instId":"BTC-USDT-SWAP","pos":"1","avgPx":"42000","upl":"2"}],
2893 "assetData":[{"ccy":"USDT","eq":"1000"}]}]}"#;
2894 let mock = MockTransport::new(body);
2895 let client = signed_client(mock.clone());
2896 let request = super::PositionBuilderRequest::new()
2897 .account_level("2")
2898 .include_real_positions_and_equity(false)
2899 .simulated_positions(vec![
2900 super::SimulatedPosition::new("BTC-USDT-SWAP").position("1"),
2901 ])
2902 .simulated_assets(vec![super::SimulatedAsset::new("USDT").equity("1000")]);
2903
2904 let result = client.account().position_builder(&request).await.unwrap();
2905 assert_eq!(result[0].pos_data[0].inst_id, "BTC-USDT-SWAP");
2906 assert_eq!(result[0].asset_data[0].eq.as_str(), "1000");
2907
2908 let req = mock.captured();
2909 assert_eq!(req.method, http::Method::POST);
2910 assert!(req.uri.ends_with("/api/v5/account/position-builder"));
2911 let sent: serde_json::Value = serde_json::from_str(req.body_str()).unwrap();
2912 assert_eq!(sent["acctLv"], "2");
2913 assert_eq!(sent["inclRealPosAndEq"], false);
2914 assert_eq!(sent["simPos"][0]["instId"], "BTC-USDT-SWAP");
2915 assert_eq!(sent["simAsset"][0]["ccy"], "USDT");
2916 assert!(sent.get("lever").is_none());
2917 assert!(sent["simPos"][0].get("avgPx").is_none());
2918 assert!(req.is_signed());
2919 }
2920}