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)]
835mod tests {
836 use rstest::rstest;
837 use serde_json;
838
839 use super::*;
840
841 #[rstest]
842 fn test_algo_order_request_serialization() {
843 let request = OKXPlaceAlgoOrderRequest {
844 inst_id: "ETH-USDT-SWAP".to_string(),
845 td_mode: OKXTradeMode::Isolated,
846 side: OKXSide::Buy,
847 ord_type: OKXAlgoOrderType::Trigger,
848 sz: "0.01".to_string(),
849 algo_cl_ord_id: Some("test123".to_string()),
850 trigger_px: Some("3000".to_string()),
851 order_px: Some("-1".to_string()),
852 trigger_px_type: Some(OKXTriggerType::Last),
853 tgt_ccy: None,
854 pos_side: None,
855 close_position: None,
856 tag: None,
857 reduce_only: None,
858 };
859
860 let json = serde_json::to_string(&request).unwrap();
861
862 assert!(json.contains("\"instId\":\"ETH-USDT-SWAP\""));
864 assert!(json.contains("\"tdMode\":\"isolated\""));
865 assert!(json.contains("\"ordType\":\"trigger\""));
866 assert!(json.contains("\"algoClOrdId\":\"test123\""));
867 assert!(json.contains("\"triggerPx\":\"3000\""));
868 assert!(json.contains("\"orderPx\":\"-1\""));
869 assert!(json.contains("\"triggerPxType\":\"last\""));
870
871 assert!(!json.contains("tgtCcy"));
873 assert!(!json.contains("posSide"));
874 assert!(!json.contains("closePosition"));
875 }
876
877 #[rstest]
878 fn test_algo_order_request_array_serialization() {
879 let request = OKXPlaceAlgoOrderRequest {
880 inst_id: "BTC-USDT".to_string(),
881 td_mode: OKXTradeMode::Cross,
882 side: OKXSide::Sell,
883 ord_type: OKXAlgoOrderType::Trigger,
884 sz: "0.1".to_string(),
885 algo_cl_ord_id: None,
886 trigger_px: Some("50000".to_string()),
887 order_px: Some("49900".to_string()),
888 trigger_px_type: Some(OKXTriggerType::Mark),
889 tgt_ccy: Some(OKXTargetCurrency::BaseCcy),
890 pos_side: Some(OKXPositionSide::Net),
891 close_position: None,
892 tag: None,
893 reduce_only: Some(true),
894 };
895
896 let json = serde_json::to_string(&[request]).unwrap();
898
899 assert!(json.starts_with('['));
901 assert!(json.ends_with(']'));
902
903 assert!(json.contains("\"instId\":\"BTC-USDT\""));
905 assert!(json.contains("\"tdMode\":\"cross\""));
906 assert!(json.contains("\"triggerPx\":\"50000\""));
907 assert!(json.contains("\"orderPx\":\"49900\""));
908 assert!(json.contains("\"triggerPxType\":\"mark\""));
909 assert!(json.contains("\"tgtCcy\":\"base_ccy\""));
910 assert!(json.contains("\"posSide\":\"net\""));
911 assert!(json.contains("\"reduceOnly\":true"));
912 }
913
914 #[rstest]
915 fn test_cancel_algo_order_request_serialization() {
916 let request = OKXCancelAlgoOrderRequest {
917 inst_id: "ETH-USDT-SWAP".to_string(),
918 algo_id: Some("123456".to_string()),
919 algo_cl_ord_id: None,
920 };
921
922 let json = serde_json::to_string(&request).unwrap();
923
924 assert!(json.contains("\"instId\":\"ETH-USDT-SWAP\""));
926 assert!(json.contains("\"algoId\":\"123456\""));
927 assert!(!json.contains("algoClOrdId"));
928 }
929
930 #[rstest]
931 fn test_cancel_algo_order_with_client_id_serialization() {
932 let request = OKXCancelAlgoOrderRequest {
933 inst_id: "BTC-USDT".to_string(),
934 algo_id: None,
935 algo_cl_ord_id: Some("client123".to_string()),
936 };
937
938 let json = serde_json::to_string(&[request]).unwrap();
940
941 assert!(json.starts_with('['));
943 assert!(json.contains("\"instId\":\"BTC-USDT\""));
944 assert!(json.contains("\"algoClOrdId\":\"client123\""));
945 assert!(!json.contains("\"algoId\""));
946 }
947}