1use serde::{Deserialize, Serialize};
19use ustr::Ustr;
20
21use crate::common::parse::{
22 deserialize_empty_string_as_none, deserialize_empty_ustr_as_none,
23 deserialize_target_currency_as_none,
24};
25
26#[derive(Clone, Debug, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct OKXTrade {
30 pub inst_id: Ustr,
32 pub px: String,
34 pub sz: String,
36 pub side: OKXSide,
38 pub trade_id: Ustr,
40 #[serde(deserialize_with = "deserialize_string_to_u64")]
42 pub ts: u64,
43}
44
45#[derive(Clone, Debug, Serialize, Deserialize)]
48pub struct OKXCandlestick(
49 pub String,
51 pub String,
53 pub String,
55 pub String,
57 pub String,
59 pub String,
61 pub String,
63 pub String,
65 pub String,
67);
68
69use crate::common::{
70 enums::{
71 OKXAlgoOrderType, OKXExecType, OKXInstrumentType, OKXMarginMode, OKXOrderCategory,
72 OKXOrderStatus, OKXOrderType, OKXPositionSide, OKXSide, OKXTargetCurrency, OKXTradeMode,
73 OKXTriggerType, OKXVipLevel,
74 },
75 parse::deserialize_string_to_u64,
76};
77
78#[derive(Clone, Debug, Serialize, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct OKXMarkPrice {
82 pub uly: Option<Ustr>,
84 pub inst_id: Ustr,
86 pub mark_px: String,
88 #[serde(deserialize_with = "deserialize_string_to_u64")]
90 pub ts: u64,
91}
92
93#[derive(Clone, Debug, Serialize, Deserialize)]
95#[serde(rename_all = "camelCase")]
96pub struct OKXIndexTicker {
97 pub inst_id: Ustr,
99 pub idx_px: String,
101 #[serde(deserialize_with = "deserialize_string_to_u64")]
103 pub ts: u64,
104}
105
106#[derive(Clone, Debug, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct OKXPositionTier {
110 pub uly: Ustr,
112 pub inst_family: String,
114 pub inst_id: Ustr,
116 pub tier: String,
118 pub min_sz: String,
120 pub max_sz: String,
122 pub mmr: String,
124 pub imr: String,
126 pub max_lever: String,
128 pub opt_mgn_factor: String,
130 pub quote_max_loan: String,
132 pub base_max_loan: String,
134}
135
136#[derive(Clone, Debug, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct OKXAccount {
140 pub adj_eq: String,
142 pub borrow_froz: String,
144 pub details: Vec<OKXBalanceDetail>,
146 pub imr: String,
148 pub iso_eq: String,
150 pub mgn_ratio: String,
152 pub mmr: String,
154 pub notional_usd_for_borrow: String,
156 pub notional_usd_for_futures: String,
158 pub notional_usd_for_option: String,
160 pub notional_usd_for_swap: String,
162 pub notional_usd: String,
164 pub ord_froz: String,
166 pub total_eq: String,
168 #[serde(deserialize_with = "deserialize_string_to_u64")]
170 pub u_time: u64,
171 pub upl: String,
173}
174
175#[derive(Clone, Debug, Serialize, Deserialize)]
177#[serde(rename_all = "camelCase")]
178#[cfg_attr(feature = "python", pyo3::pyclass)]
179pub struct OKXBalanceDetail {
180 pub avail_bal: String,
182 pub avail_eq: String,
184 pub borrow_froz: String,
186 pub cash_bal: String,
188 pub ccy: Ustr,
190 pub cross_liab: String,
192 pub dis_eq: String,
194 pub eq: String,
196 pub eq_usd: String,
198 pub smt_sync_eq: String,
200 pub spot_copy_trading_eq: String,
202 pub fixed_bal: String,
204 pub frozen_bal: String,
206 pub imr: String,
208 pub interest: String,
210 pub iso_eq: String,
212 pub iso_liab: String,
214 pub iso_upl: String,
216 pub liab: String,
218 pub max_loan: String,
220 pub mgn_ratio: String,
222 pub mmr: String,
224 pub notional_lever: String,
226 pub ord_frozen: String,
228 pub reward_bal: String,
230 #[serde(alias = "spotInUse")]
232 pub spot_in_use_amt: String,
233 #[serde(alias = "clSpotInUse")]
235 pub cl_spot_in_use_amt: String,
236 #[serde(alias = "maxSpotInUse")]
238 pub max_spot_in_use_amt: String,
239 pub spot_iso_bal: String,
241 pub stgy_eq: String,
243 pub twap: String,
245 #[serde(deserialize_with = "deserialize_string_to_u64")]
247 pub u_time: u64,
248 pub upl: String,
250 pub upl_liab: String,
252 pub spot_bal: String,
254 pub open_avg_px: String,
256 pub acc_avg_px: String,
258 pub spot_upl: String,
260 pub spot_upl_ratio: String,
262 pub total_pnl: String,
264 pub total_pnl_ratio: String,
266}
267
268#[derive(Clone, Debug, Serialize, Deserialize)]
270#[serde(rename_all = "camelCase")]
271pub struct OKXPosition {
272 pub inst_id: Ustr,
274 pub inst_type: OKXInstrumentType,
276 pub mgn_mode: OKXMarginMode,
278 #[serde(default, deserialize_with = "deserialize_empty_ustr_as_none")]
280 pub pos_id: Option<Ustr>,
281 pub pos_side: OKXPositionSide,
283 pub pos: String,
285 pub base_bal: String,
287 pub ccy: String,
289 pub fee: String,
291 pub lever: String,
293 pub last: String,
295 pub mark_px: String,
297 pub liq_px: String,
299 pub mmr: String,
301 pub interest: String,
303 pub trade_id: Ustr,
305 pub notional_usd: String,
307 pub avg_px: String,
309 pub upl: String,
311 pub upl_ratio: String,
313 #[serde(deserialize_with = "deserialize_string_to_u64")]
315 pub u_time: u64,
316 pub margin: String,
318 pub mgn_ratio: String,
320 pub adl: String,
322 pub c_time: String,
324 pub realized_pnl: String,
326 pub upl_last_px: String,
328 pub upl_ratio_last_px: String,
330 pub avail_pos: String,
332 pub be_px: String,
334 pub funding_fee: String,
336 pub idx_px: String,
338 pub liq_penalty: String,
340 pub opt_val: String,
342 pub pending_close_ord_liab_val: String,
344 pub pnl: String,
346 pub pos_ccy: String,
348 pub quote_bal: String,
350 pub quote_borrowed: String,
352 pub quote_interest: String,
354 #[serde(alias = "spotInUse")]
356 pub spot_in_use_amt: String,
357 pub spot_in_use_ccy: String,
359 pub usd_px: String,
361}
362
363#[derive(Clone, Debug, Serialize, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct OKXPlaceOrderResponse {
368 #[serde(default)]
370 pub ord_id: Option<Ustr>,
371 #[serde(default)]
373 pub cl_ord_id: Option<Ustr>,
374 #[serde(default)]
376 pub tag: Option<String>,
377 #[serde(default)]
379 pub inst_id: Option<Ustr>,
380 #[serde(default)]
382 pub side: Option<OKXSide>,
383 #[serde(default)]
385 pub ord_type: Option<OKXOrderType>,
386 #[serde(default)]
388 pub sz: Option<String>,
389 pub state: Option<OKXOrderStatus>,
391 #[serde(default)]
393 pub px: Option<String>,
394 #[serde(default)]
396 pub avg_px: Option<String>,
397 #[serde(default)]
399 pub acc_fill_sz: Option<String>,
400 #[serde(default)]
402 pub fill_sz: Option<String>,
403 #[serde(default)]
405 pub fill_px: Option<String>,
406 #[serde(default)]
408 pub trade_id: Option<Ustr>,
409 #[serde(default)]
411 pub fill_time: Option<String>,
412 #[serde(default)]
414 pub fee: Option<String>,
415 #[serde(default)]
417 pub fee_ccy: Option<String>,
418 #[serde(default)]
420 pub req_id: Option<Ustr>,
421 #[serde(default)]
423 pub pos_side: Option<OKXPositionSide>,
424 #[serde(default)]
426 pub reduce_only: Option<String>,
427 #[serde(default, deserialize_with = "deserialize_target_currency_as_none")]
429 pub tgt_ccy: Option<OKXTargetCurrency>,
430 #[serde(default)]
432 pub c_time: Option<String>,
433 #[serde(default)]
435 pub u_time: Option<String>,
436}
437
438#[derive(Clone, Debug, Serialize, Deserialize)]
440#[serde(rename_all = "camelCase")]
441pub struct OKXOrderHistory {
442 pub ord_id: Ustr,
444 pub cl_ord_id: Ustr,
446 #[serde(default)]
448 pub algo_id: Option<Ustr>,
449 #[serde(default)]
451 pub algo_cl_ord_id: Option<Ustr>,
452 #[serde(default)]
454 pub cl_act_id: Option<Ustr>,
455 pub tag: String,
457 pub inst_type: OKXInstrumentType,
459 pub uly: Option<Ustr>,
461 pub inst_id: Ustr,
463 pub ord_type: OKXOrderType,
465 pub sz: String,
467 pub px: String,
469 pub side: OKXSide,
471 pub pos_side: OKXPositionSide,
473 pub td_mode: OKXTradeMode,
475 pub reduce_only: String,
477 #[serde(default, deserialize_with = "deserialize_target_currency_as_none")]
479 pub tgt_ccy: Option<OKXTargetCurrency>,
480 pub state: OKXOrderStatus,
482 pub avg_px: String,
484 pub fee: String,
486 pub fee_ccy: String,
488 pub fill_sz: String,
490 pub fill_px: String,
492 pub trade_id: Ustr,
494 #[serde(deserialize_with = "deserialize_string_to_u64")]
496 pub fill_time: u64,
497 pub acc_fill_sz: String,
499 #[serde(default)]
501 pub fill_fee: Option<String>,
502 #[serde(default)]
504 pub req_id: Option<Ustr>,
505 #[serde(default)]
507 pub cancel_fill_sz: Option<String>,
508 #[serde(default)]
510 pub cancel_total_sz: Option<String>,
511 #[serde(default)]
513 pub fee_discount: Option<String>,
514 pub category: OKXOrderCategory,
516 #[serde(deserialize_with = "deserialize_string_to_u64")]
518 pub u_time: u64,
519 #[serde(deserialize_with = "deserialize_string_to_u64")]
521 pub c_time: u64,
522}
523
524#[derive(Clone, Debug, Serialize, Deserialize)]
526#[serde(rename_all = "camelCase")]
527pub struct OKXOrderAlgo {
528 pub algo_id: String,
530 #[serde(default)]
532 pub algo_cl_ord_id: String,
533 #[serde(default)]
535 pub cl_ord_id: String,
536 #[serde(default)]
538 pub ord_id: String,
539 pub inst_id: Ustr,
541 pub inst_type: OKXInstrumentType,
543 pub ord_type: OKXOrderType,
545 pub state: OKXOrderStatus,
547 pub side: OKXSide,
549 pub pos_side: OKXPositionSide,
551 pub sz: String,
553 #[serde(default)]
555 pub trigger_px: String,
556 #[serde(default)]
558 pub trigger_px_type: Option<OKXTriggerType>,
559 #[serde(default)]
561 pub ord_px: String,
562 pub td_mode: OKXTradeMode,
564 #[serde(default)]
566 pub lever: String,
567 #[serde(default)]
569 pub reduce_only: String,
570 #[serde(default)]
572 pub actual_px: String,
573 #[serde(default)]
575 pub actual_sz: String,
576 #[serde(default)]
578 pub notional_usd: String,
579 #[serde(deserialize_with = "deserialize_string_to_u64")]
581 pub c_time: u64,
582 #[serde(deserialize_with = "deserialize_string_to_u64")]
584 pub u_time: u64,
585 #[serde(default)]
587 pub trigger_time: String,
588 #[serde(default)]
590 pub tag: String,
591}
592
593#[derive(Clone, Debug, Serialize, Deserialize)]
595#[serde(rename_all = "camelCase")]
596pub struct OKXTransactionDetail {
597 pub inst_type: OKXInstrumentType,
599 pub inst_id: Ustr,
601 pub trade_id: Ustr,
603 pub ord_id: Ustr,
605 pub cl_ord_id: Ustr,
607 pub bill_id: Ustr,
609 pub fill_px: String,
611 pub fill_sz: String,
613 pub side: OKXSide,
615 pub exec_type: OKXExecType,
617 pub fee_ccy: String,
619 #[serde(default, deserialize_with = "deserialize_empty_string_as_none")]
621 pub fee: Option<String>,
622 #[serde(deserialize_with = "deserialize_string_to_u64")]
624 pub ts: u64,
625}
626
627#[derive(Clone, Debug, Serialize, Deserialize)]
629#[serde(rename_all = "camelCase")]
630pub struct OKXPositionHistory {
631 pub inst_type: OKXInstrumentType,
633 pub inst_id: Ustr,
635 pub mgn_mode: OKXMarginMode,
637 #[serde(rename = "type")]
640 pub r#type: Ustr,
641 pub c_time: String,
643 #[serde(deserialize_with = "deserialize_string_to_u64")]
645 pub u_time: u64,
646 pub open_avg_px: String,
648 #[serde(skip_serializing_if = "Option::is_none")]
650 pub close_avg_px: Option<String>,
651 #[serde(default, deserialize_with = "deserialize_empty_ustr_as_none")]
653 pub pos_id: Option<Ustr>,
654 #[serde(skip_serializing_if = "Option::is_none")]
656 pub open_max_pos: Option<String>,
657 #[serde(skip_serializing_if = "Option::is_none")]
659 pub close_total_pos: Option<String>,
660 #[serde(skip_serializing_if = "Option::is_none")]
662 pub realized_pnl: Option<String>,
663 #[serde(skip_serializing_if = "Option::is_none")]
665 pub fee: Option<String>,
666 #[serde(skip_serializing_if = "Option::is_none")]
668 pub funding_fee: Option<String>,
669 #[serde(skip_serializing_if = "Option::is_none")]
671 pub liq_penalty: Option<String>,
672 #[serde(skip_serializing_if = "Option::is_none")]
674 pub pnl: Option<String>,
675 #[serde(skip_serializing_if = "Option::is_none")]
677 pub pnl_ratio: Option<String>,
678 pub pos_side: OKXPositionSide,
680 pub lever: String,
682 #[serde(skip_serializing_if = "Option::is_none")]
684 pub direction: Option<String>,
685 #[serde(skip_serializing_if = "Option::is_none")]
687 pub trigger_px: Option<String>,
688 #[serde(skip_serializing_if = "Option::is_none")]
690 pub uly: Option<String>,
691 #[serde(skip_serializing_if = "Option::is_none")]
693 pub ccy: Option<String>,
694}
695
696#[derive(Clone, Debug, Serialize, Deserialize)]
698#[serde(rename_all = "camelCase")]
699pub struct OKXPlaceAlgoOrderRequest {
700 #[serde(rename = "instId")]
702 pub inst_id: String,
703 #[serde(rename = "tdMode")]
705 pub td_mode: OKXTradeMode,
706 pub side: OKXSide,
708 #[serde(rename = "ordType")]
710 pub ord_type: OKXAlgoOrderType,
711 pub sz: String,
713 #[serde(rename = "algoClOrdId", skip_serializing_if = "Option::is_none")]
715 pub algo_cl_ord_id: Option<String>,
716 #[serde(rename = "triggerPx", skip_serializing_if = "Option::is_none")]
718 pub trigger_px: Option<String>,
719 #[serde(rename = "orderPx", skip_serializing_if = "Option::is_none")]
721 pub order_px: Option<String>,
722 #[serde(rename = "triggerPxType", skip_serializing_if = "Option::is_none")]
724 pub trigger_px_type: Option<OKXTriggerType>,
725 #[serde(rename = "tgtCcy", skip_serializing_if = "Option::is_none")]
727 pub tgt_ccy: Option<OKXTargetCurrency>,
728 #[serde(rename = "posSide", skip_serializing_if = "Option::is_none")]
730 pub pos_side: Option<OKXPositionSide>,
731 #[serde(rename = "closePosition", skip_serializing_if = "Option::is_none")]
733 pub close_position: Option<bool>,
734 #[serde(skip_serializing_if = "Option::is_none")]
736 pub tag: Option<String>,
737 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
739 pub reduce_only: Option<bool>,
740}
741
742#[derive(Clone, Debug, Serialize, Deserialize)]
744#[serde(rename_all = "camelCase")]
745pub struct OKXPlaceAlgoOrderResponse {
746 pub algo_id: String,
748 #[serde(skip_serializing_if = "Option::is_none")]
750 pub algo_cl_ord_id: Option<String>,
751 #[serde(skip_serializing_if = "Option::is_none")]
753 pub s_code: Option<String>,
754 #[serde(skip_serializing_if = "Option::is_none")]
756 pub s_msg: Option<String>,
757 #[serde(skip_serializing_if = "Option::is_none")]
759 pub req_id: Option<String>,
760}
761
762#[derive(Clone, Debug, Serialize, Deserialize)]
764#[serde(rename_all = "camelCase")]
765pub struct OKXCancelAlgoOrderRequest {
766 pub inst_id: String,
768 #[serde(skip_serializing_if = "Option::is_none")]
770 pub algo_id: Option<String>,
771 #[serde(skip_serializing_if = "Option::is_none")]
773 pub algo_cl_ord_id: Option<String>,
774}
775
776#[derive(Clone, Debug, Serialize, Deserialize)]
778#[serde(rename_all = "camelCase")]
779pub struct OKXCancelAlgoOrderResponse {
780 pub algo_id: String,
782 #[serde(skip_serializing_if = "Option::is_none")]
784 pub s_code: Option<String>,
785 #[serde(skip_serializing_if = "Option::is_none")]
787 pub s_msg: Option<String>,
788}
789
790#[derive(Clone, Debug, Serialize, Deserialize)]
792#[serde(rename_all = "camelCase")]
793pub struct OKXServerTime {
794 #[serde(deserialize_with = "deserialize_string_to_u64")]
796 pub ts: u64,
797}
798
799#[derive(Clone, Debug, Serialize, Deserialize)]
801#[serde(rename_all = "camelCase")]
802pub struct OKXFeeRate {
803 #[serde(deserialize_with = "crate::common::parse::deserialize_vip_level")]
805 pub level: OKXVipLevel,
806 pub taker: String,
808 pub maker: String,
810 pub taker_u: String,
812 pub maker_u: String,
814 #[serde(default)]
816 pub delivery: String,
817 #[serde(default)]
819 pub exercise: String,
820 pub inst_type: OKXInstrumentType,
822 #[serde(default)]
824 pub category: String,
825 #[serde(deserialize_with = "deserialize_string_to_u64")]
827 pub ts: u64,
828}
829
830#[cfg(test)]
831mod tests {
832 use rstest::rstest;
833 use serde_json;
834
835 use super::*;
836
837 #[rstest]
838 fn test_algo_order_request_serialization() {
839 let request = OKXPlaceAlgoOrderRequest {
840 inst_id: "ETH-USDT-SWAP".to_string(),
841 td_mode: OKXTradeMode::Isolated,
842 side: OKXSide::Buy,
843 ord_type: OKXAlgoOrderType::Trigger,
844 sz: "0.01".to_string(),
845 algo_cl_ord_id: Some("test123".to_string()),
846 trigger_px: Some("3000".to_string()),
847 order_px: Some("-1".to_string()),
848 trigger_px_type: Some(OKXTriggerType::Last),
849 tgt_ccy: None,
850 pos_side: None,
851 close_position: None,
852 tag: None,
853 reduce_only: None,
854 };
855
856 let json = serde_json::to_string(&request).unwrap();
857
858 assert!(json.contains("\"instId\":\"ETH-USDT-SWAP\""));
860 assert!(json.contains("\"tdMode\":\"isolated\""));
861 assert!(json.contains("\"ordType\":\"trigger\""));
862 assert!(json.contains("\"algoClOrdId\":\"test123\""));
863 assert!(json.contains("\"triggerPx\":\"3000\""));
864 assert!(json.contains("\"orderPx\":\"-1\""));
865 assert!(json.contains("\"triggerPxType\":\"last\""));
866
867 assert!(!json.contains("tgtCcy"));
869 assert!(!json.contains("posSide"));
870 assert!(!json.contains("closePosition"));
871 }
872
873 #[rstest]
874 fn test_algo_order_request_array_serialization() {
875 let request = OKXPlaceAlgoOrderRequest {
876 inst_id: "BTC-USDT".to_string(),
877 td_mode: OKXTradeMode::Cross,
878 side: OKXSide::Sell,
879 ord_type: OKXAlgoOrderType::Trigger,
880 sz: "0.1".to_string(),
881 algo_cl_ord_id: None,
882 trigger_px: Some("50000".to_string()),
883 order_px: Some("49900".to_string()),
884 trigger_px_type: Some(OKXTriggerType::Mark),
885 tgt_ccy: Some(OKXTargetCurrency::BaseCcy),
886 pos_side: Some(OKXPositionSide::Net),
887 close_position: None,
888 tag: None,
889 reduce_only: Some(true),
890 };
891
892 let json = serde_json::to_string(&[request]).unwrap();
894
895 assert!(json.starts_with('['));
897 assert!(json.ends_with(']'));
898
899 assert!(json.contains("\"instId\":\"BTC-USDT\""));
901 assert!(json.contains("\"tdMode\":\"cross\""));
902 assert!(json.contains("\"triggerPx\":\"50000\""));
903 assert!(json.contains("\"orderPx\":\"49900\""));
904 assert!(json.contains("\"triggerPxType\":\"mark\""));
905 assert!(json.contains("\"tgtCcy\":\"base_ccy\""));
906 assert!(json.contains("\"posSide\":\"net\""));
907 assert!(json.contains("\"reduceOnly\":true"));
908 }
909
910 #[rstest]
911 fn test_cancel_algo_order_request_serialization() {
912 let request = OKXCancelAlgoOrderRequest {
913 inst_id: "ETH-USDT-SWAP".to_string(),
914 algo_id: Some("123456".to_string()),
915 algo_cl_ord_id: None,
916 };
917
918 let json = serde_json::to_string(&request).unwrap();
919
920 assert!(json.contains("\"instId\":\"ETH-USDT-SWAP\""));
922 assert!(json.contains("\"algoId\":\"123456\""));
923 assert!(!json.contains("algoClOrdId"));
924 }
925
926 #[rstest]
927 fn test_cancel_algo_order_with_client_id_serialization() {
928 let request = OKXCancelAlgoOrderRequest {
929 inst_id: "BTC-USDT".to_string(),
930 algo_id: None,
931 algo_cl_ord_id: Some("client123".to_string()),
932 };
933
934 let json = serde_json::to_string(&[request]).unwrap();
936
937 assert!(json.starts_with('['));
939 assert!(json.contains("\"instId\":\"BTC-USDT\""));
940 assert!(json.contains("\"algoClOrdId\":\"client123\""));
941 assert!(!json.contains("\"algoId\""));
942 }
943}