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 OKXOptionSummary {
97 pub inst_type: OKXInstrumentType,
99 pub inst_id: Ustr,
101 pub uly: Ustr,
103 pub bid_vol: String,
105 pub ask_vol: String,
107 pub mark_vol: String,
109 pub fwd_px: String,
111 #[serde(deserialize_with = "deserialize_string_to_u64")]
113 pub ts: u64,
114}
115
116#[derive(Clone, Debug, Serialize, Deserialize)]
118#[serde(rename_all = "camelCase")]
119pub struct OKXIndexTicker {
120 pub inst_id: Ustr,
122 pub idx_px: String,
124 #[serde(deserialize_with = "deserialize_string_to_u64")]
126 pub ts: u64,
127}
128
129pub type OKXOrderBookLevel = (String, String, String, String);
132
133#[derive(Clone, Debug, Serialize, Deserialize)]
135#[serde(rename_all = "camelCase")]
136pub struct OKXOrderBookSnapshot {
137 pub asks: Vec<OKXOrderBookLevel>,
139 pub bids: Vec<OKXOrderBookLevel>,
141 #[serde(deserialize_with = "deserialize_string_to_u64")]
143 pub ts: u64,
144}
145
146#[derive(Clone, Debug, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct OKXFundingRateHistory {
150 pub inst_type: OKXInstrumentType,
152 pub inst_id: Ustr,
154 pub funding_rate: String,
156 pub realized_rate: String,
158 #[serde(deserialize_with = "deserialize_string_to_u64")]
160 pub funding_time: u64,
161 #[serde(default)]
163 pub method: Option<String>,
164}
165
166#[derive(Clone, Debug, Serialize, Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct OKXPositionTier {
170 pub uly: Ustr,
172 pub inst_family: String,
174 pub inst_id: Ustr,
176 pub tier: String,
178 pub min_sz: String,
180 pub max_sz: String,
182 pub mmr: String,
184 pub imr: String,
186 pub max_lever: String,
188 pub opt_mgn_factor: String,
190 pub quote_max_loan: String,
192 pub base_max_loan: String,
194}
195
196#[derive(Clone, Debug, Serialize, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct OKXAccount {
200 pub adj_eq: String,
202 pub borrow_froz: String,
204 pub details: Vec<OKXBalanceDetail>,
206 pub imr: String,
208 pub iso_eq: String,
210 pub mgn_ratio: String,
212 pub mmr: String,
214 pub notional_usd_for_borrow: String,
216 pub notional_usd_for_futures: String,
218 pub notional_usd_for_option: String,
220 pub notional_usd_for_swap: String,
222 pub notional_usd: String,
224 pub ord_froz: String,
226 pub total_eq: String,
228 #[serde(deserialize_with = "deserialize_string_to_u64")]
230 pub u_time: u64,
231 pub upl: String,
233}
234
235#[derive(Clone, Debug, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238#[cfg_attr(feature = "python", pyo3::pyclass(from_py_object))]
239#[cfg_attr(
240 feature = "python",
241 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.okx")
242)]
243pub struct OKXBalanceDetail {
244 pub avail_bal: String,
246 pub avail_eq: String,
248 pub borrow_froz: String,
250 pub cash_bal: String,
252 pub ccy: Ustr,
254 pub cross_liab: String,
256 pub dis_eq: String,
258 pub eq: String,
260 pub eq_usd: String,
262 pub smt_sync_eq: String,
264 pub spot_copy_trading_eq: String,
266 pub fixed_bal: String,
268 pub frozen_bal: String,
270 pub imr: String,
272 pub interest: String,
274 pub iso_eq: String,
276 pub iso_liab: String,
278 pub iso_upl: String,
280 pub liab: String,
282 pub max_loan: String,
284 pub mgn_ratio: String,
286 pub mmr: String,
288 pub notional_lever: String,
290 pub ord_frozen: String,
292 pub reward_bal: String,
294 #[serde(alias = "spotInUse")]
296 pub spot_in_use_amt: String,
297 #[serde(alias = "clSpotInUse")]
299 pub cl_spot_in_use_amt: String,
300 #[serde(alias = "maxSpotInUse")]
302 pub max_spot_in_use_amt: String,
303 pub spot_iso_bal: String,
305 pub stgy_eq: String,
307 pub twap: String,
309 #[serde(deserialize_with = "deserialize_string_to_u64")]
311 pub u_time: u64,
312 pub upl: String,
314 pub upl_liab: String,
316 pub spot_bal: String,
318 pub open_avg_px: String,
320 pub acc_avg_px: String,
322 pub spot_upl: String,
324 pub spot_upl_ratio: String,
326 pub total_pnl: String,
328 pub total_pnl_ratio: String,
330}
331
332#[derive(Clone, Debug, Serialize, Deserialize)]
334#[serde(rename_all = "camelCase")]
335pub struct OKXPosition {
336 pub inst_id: Ustr,
338 pub inst_type: OKXInstrumentType,
340 pub mgn_mode: OKXMarginMode,
342 #[serde(default, deserialize_with = "deserialize_empty_ustr_as_none")]
344 pub pos_id: Option<Ustr>,
345 pub pos_side: OKXPositionSide,
347 pub pos: String,
349 pub base_bal: String,
351 pub ccy: String,
353 pub fee: String,
355 pub lever: String,
357 pub last: String,
359 pub mark_px: String,
361 pub liq_px: String,
363 pub mmr: String,
365 pub interest: String,
367 pub trade_id: Ustr,
369 pub notional_usd: String,
371 pub avg_px: String,
373 pub upl: String,
375 pub upl_ratio: String,
377 #[serde(deserialize_with = "deserialize_string_to_u64")]
379 pub u_time: u64,
380 pub margin: String,
382 pub mgn_ratio: String,
384 pub adl: String,
386 pub c_time: String,
388 pub realized_pnl: String,
390 pub upl_last_px: String,
392 pub upl_ratio_last_px: String,
394 pub avail_pos: String,
396 pub be_px: String,
398 pub funding_fee: String,
400 pub idx_px: String,
402 pub liq_penalty: String,
404 pub opt_val: String,
406 pub pending_close_ord_liab_val: String,
408 pub pnl: String,
410 pub pos_ccy: String,
412 pub quote_bal: String,
414 pub quote_borrowed: String,
416 pub quote_interest: String,
418 #[serde(alias = "spotInUse")]
420 pub spot_in_use_amt: String,
421 pub spot_in_use_ccy: String,
423 pub usd_px: String,
425 #[serde(default)]
427 pub delta_bs: String,
428 #[serde(default)]
430 pub gamma_bs: String,
431 #[serde(default)]
433 pub theta_bs: String,
434 #[serde(default)]
436 pub vega_bs: String,
437}
438
439#[derive(Clone, Debug, Serialize, Deserialize)]
442#[serde(rename_all = "camelCase")]
443pub struct OKXPlaceOrderResponse {
444 #[serde(default)]
446 pub ord_id: Option<Ustr>,
447 #[serde(default)]
449 pub cl_ord_id: Option<Ustr>,
450 #[serde(default)]
452 pub tag: Option<String>,
453 #[serde(default)]
455 pub inst_id: Option<Ustr>,
456 #[serde(default)]
458 pub side: Option<OKXSide>,
459 #[serde(default)]
461 pub ord_type: Option<OKXOrderType>,
462 #[serde(default)]
464 pub sz: Option<String>,
465 pub state: Option<OKXOrderStatus>,
467 #[serde(default)]
469 pub px: Option<String>,
470 #[serde(default)]
472 pub avg_px: Option<String>,
473 #[serde(default)]
475 pub acc_fill_sz: Option<String>,
476 #[serde(default)]
478 pub fill_sz: Option<String>,
479 #[serde(default)]
481 pub fill_px: Option<String>,
482 #[serde(default)]
484 pub trade_id: Option<Ustr>,
485 #[serde(default)]
487 pub fill_time: Option<String>,
488 #[serde(default)]
490 pub fee: Option<String>,
491 #[serde(default)]
493 pub fee_ccy: Option<String>,
494 #[serde(default)]
496 pub req_id: Option<Ustr>,
497 #[serde(default)]
499 pub pos_side: Option<OKXPositionSide>,
500 #[serde(default)]
502 pub reduce_only: Option<String>,
503 #[serde(default, deserialize_with = "deserialize_target_currency_as_none")]
505 pub tgt_ccy: Option<OKXTargetCurrency>,
506 #[serde(default)]
508 pub c_time: Option<String>,
509 #[serde(default)]
511 pub u_time: Option<String>,
512 #[serde(skip_serializing_if = "Option::is_none")]
514 pub s_code: Option<String>,
515 #[serde(skip_serializing_if = "Option::is_none")]
517 pub s_msg: Option<String>,
518}
519
520#[derive(Clone, Debug, Default, Serialize, Deserialize)]
522#[serde(rename_all = "camelCase")]
523pub struct OKXAttachAlgoOrdRequest {
524 #[serde(skip_serializing_if = "Option::is_none")]
526 pub attach_algo_cl_ord_id: Option<String>,
527 #[serde(skip_serializing_if = "Option::is_none")]
529 pub sl_trigger_px: Option<String>,
530 #[serde(skip_serializing_if = "Option::is_none")]
532 pub sl_ord_px: Option<String>,
533 #[serde(skip_serializing_if = "Option::is_none")]
535 pub sl_trigger_px_type: Option<OKXTriggerType>,
536 #[serde(skip_serializing_if = "Option::is_none")]
538 pub tp_trigger_px: Option<String>,
539 #[serde(skip_serializing_if = "Option::is_none")]
541 pub tp_ord_px: Option<String>,
542 #[serde(skip_serializing_if = "Option::is_none")]
544 pub tp_trigger_px_type: Option<OKXTriggerType>,
545}
546
547#[derive(Clone, Debug, Serialize, Deserialize)]
549#[serde(rename_all = "camelCase")]
550pub struct OKXPlaceOrderRequest {
551 pub inst_id: String,
553 pub td_mode: OKXTradeMode,
555 #[serde(skip_serializing_if = "Option::is_none")]
557 pub ccy: Option<String>,
558 #[serde(skip_serializing_if = "Option::is_none")]
560 pub cl_ord_id: Option<String>,
561 #[serde(skip_serializing_if = "Option::is_none")]
563 pub tag: Option<String>,
564 pub side: OKXSide,
566 #[serde(skip_serializing_if = "Option::is_none")]
568 pub pos_side: Option<OKXPositionSide>,
569 pub ord_type: OKXOrderType,
571 pub sz: String,
573 #[serde(skip_serializing_if = "Option::is_none")]
575 pub px: Option<String>,
576 #[serde(rename = "pxUsd", skip_serializing_if = "Option::is_none")]
578 pub px_usd: Option<String>,
579 #[serde(rename = "pxVol", skip_serializing_if = "Option::is_none")]
582 pub px_vol: Option<String>,
583 #[serde(skip_serializing_if = "Option::is_none")]
585 pub reduce_only: Option<bool>,
586 #[serde(skip_serializing_if = "Option::is_none")]
588 pub tgt_ccy: Option<OKXTargetCurrency>,
589 #[serde(skip_serializing_if = "Option::is_none")]
591 pub attach_algo_ords: Option<Vec<OKXAttachAlgoOrdRequest>>,
592}
593
594pub use crate::common::models::OKXAttachedAlgoOrd;
595
596#[derive(Clone, Debug, Serialize, Deserialize)]
598#[serde(rename_all = "camelCase")]
599pub struct OKXOrderHistory {
600 pub ord_id: Ustr,
602 pub cl_ord_id: Ustr,
604 #[serde(default)]
606 pub algo_id: Option<Ustr>,
607 #[serde(default)]
609 pub algo_cl_ord_id: Option<Ustr>,
610 #[serde(default, deserialize_with = "deserialize_empty_string_as_none")]
612 pub attach_algo_cl_ord_id: Option<String>,
613 #[serde(default)]
615 pub attach_algo_ords: Vec<OKXAttachedAlgoOrd>,
616 #[serde(default)]
618 pub cl_act_id: Option<Ustr>,
619 pub tag: String,
621 pub inst_type: OKXInstrumentType,
623 pub uly: Option<Ustr>,
625 pub inst_id: Ustr,
627 pub ord_type: OKXOrderType,
629 pub sz: String,
631 pub px: String,
633 #[serde(default)]
635 pub px_usd: String,
636 #[serde(default)]
638 pub px_vol: String,
639 pub side: OKXSide,
641 pub pos_side: OKXPositionSide,
643 pub td_mode: OKXTradeMode,
645 pub reduce_only: String,
647 #[serde(default, deserialize_with = "deserialize_target_currency_as_none")]
649 pub tgt_ccy: Option<OKXTargetCurrency>,
650 pub state: OKXOrderStatus,
652 pub avg_px: String,
654 pub fee: String,
656 pub fee_ccy: String,
658 pub fill_sz: String,
660 pub fill_px: String,
662 pub trade_id: Ustr,
664 #[serde(deserialize_with = "deserialize_string_to_u64")]
666 pub fill_time: u64,
667 pub acc_fill_sz: String,
669 #[serde(default)]
671 pub fill_fee: Option<String>,
672 #[serde(default)]
674 pub req_id: Option<Ustr>,
675 #[serde(default)]
677 pub cancel_fill_sz: Option<String>,
678 #[serde(default)]
680 pub cancel_total_sz: Option<String>,
681 #[serde(default)]
683 pub fee_discount: Option<String>,
684 pub category: OKXOrderCategory,
686 #[serde(deserialize_with = "deserialize_string_to_u64")]
688 pub u_time: u64,
689 #[serde(deserialize_with = "deserialize_string_to_u64")]
691 pub c_time: u64,
692}
693
694#[derive(Clone, Debug, Serialize, Deserialize)]
696#[serde(rename_all = "camelCase")]
697pub struct OKXOrderAlgo {
698 pub algo_id: String,
700 #[serde(default)]
702 pub algo_cl_ord_id: String,
703 #[serde(default)]
705 pub cl_ord_id: String,
706 #[serde(default)]
708 pub ord_id: String,
709 pub inst_id: Ustr,
711 pub inst_type: OKXInstrumentType,
713 pub ord_type: OKXAlgoOrderType,
715 pub state: OKXOrderStatus,
717 pub side: OKXSide,
719 pub pos_side: OKXPositionSide,
721 #[serde(default)]
723 pub sz: String,
724 #[serde(default)]
726 pub trigger_px: String,
727 #[serde(default)]
729 pub trigger_px_type: Option<OKXTriggerType>,
730 #[serde(default)]
732 pub sl_trigger_px: String,
733 #[serde(default)]
735 pub sl_ord_px: String,
736 #[serde(default)]
738 pub sl_trigger_px_type: Option<OKXTriggerType>,
739 #[serde(default)]
741 pub tp_trigger_px: String,
742 #[serde(default)]
744 pub tp_ord_px: String,
745 #[serde(default)]
747 pub tp_trigger_px_type: Option<OKXTriggerType>,
748 #[serde(default)]
750 pub ord_px: String,
751 pub td_mode: OKXTradeMode,
753 #[serde(default)]
755 pub lever: String,
756 #[serde(default)]
758 pub reduce_only: String,
759 #[serde(default)]
761 pub close_fraction: String,
762 #[serde(default)]
764 pub actual_px: String,
765 #[serde(default)]
767 pub actual_sz: String,
768 #[serde(default)]
770 pub notional_usd: String,
771 #[serde(deserialize_with = "deserialize_string_to_u64")]
773 pub c_time: u64,
774 #[serde(deserialize_with = "deserialize_string_to_u64")]
776 pub u_time: u64,
777 #[serde(default)]
779 pub trigger_time: String,
780 #[serde(default)]
782 pub tag: String,
783 #[serde(default)]
785 pub callback_ratio: String,
786 #[serde(default)]
788 pub callback_spread: String,
789 #[serde(default)]
791 pub active_px: String,
792}
793
794#[derive(Clone, Debug, Serialize, Deserialize)]
796#[serde(rename_all = "camelCase")]
797pub struct OKXTransactionDetail {
798 pub inst_type: OKXInstrumentType,
800 pub inst_id: Ustr,
802 pub trade_id: Ustr,
804 pub ord_id: Ustr,
806 pub cl_ord_id: Ustr,
808 pub bill_id: Ustr,
810 pub fill_px: String,
812 pub fill_sz: String,
814 pub side: OKXSide,
816 pub exec_type: OKXExecType,
818 pub fee_ccy: String,
820 #[serde(default, deserialize_with = "deserialize_empty_string_as_none")]
822 pub fee: Option<String>,
823 #[serde(deserialize_with = "deserialize_string_to_u64")]
825 pub ts: u64,
826}
827
828#[derive(Clone, Debug, Serialize, Deserialize)]
830#[serde(rename_all = "camelCase")]
831pub struct OKXPositionHistory {
832 pub inst_type: OKXInstrumentType,
834 pub inst_id: Ustr,
836 pub mgn_mode: OKXMarginMode,
838 #[serde(rename = "type")]
841 pub r#type: Ustr,
842 pub c_time: String,
844 #[serde(deserialize_with = "deserialize_string_to_u64")]
846 pub u_time: u64,
847 pub open_avg_px: String,
849 #[serde(skip_serializing_if = "Option::is_none")]
851 pub close_avg_px: Option<String>,
852 #[serde(default, deserialize_with = "deserialize_empty_ustr_as_none")]
854 pub pos_id: Option<Ustr>,
855 #[serde(skip_serializing_if = "Option::is_none")]
857 pub open_max_pos: Option<String>,
858 #[serde(skip_serializing_if = "Option::is_none")]
860 pub close_total_pos: Option<String>,
861 #[serde(skip_serializing_if = "Option::is_none")]
863 pub realized_pnl: Option<String>,
864 #[serde(skip_serializing_if = "Option::is_none")]
866 pub fee: Option<String>,
867 #[serde(skip_serializing_if = "Option::is_none")]
869 pub funding_fee: Option<String>,
870 #[serde(skip_serializing_if = "Option::is_none")]
872 pub liq_penalty: Option<String>,
873 #[serde(skip_serializing_if = "Option::is_none")]
875 pub pnl: Option<String>,
876 #[serde(skip_serializing_if = "Option::is_none")]
878 pub pnl_ratio: Option<String>,
879 pub pos_side: OKXPositionSide,
881 pub lever: String,
883 #[serde(skip_serializing_if = "Option::is_none")]
885 pub direction: Option<String>,
886 #[serde(skip_serializing_if = "Option::is_none")]
888 pub trigger_px: Option<String>,
889 #[serde(skip_serializing_if = "Option::is_none")]
891 pub uly: Option<String>,
892 #[serde(skip_serializing_if = "Option::is_none")]
894 pub ccy: Option<String>,
895}
896
897#[derive(Clone, Debug, Serialize, Deserialize)]
899#[serde(rename_all = "camelCase")]
900pub struct OKXPlaceAlgoOrderRequest {
901 #[serde(rename = "instId")]
903 pub inst_id: String,
904 #[serde(rename = "instIdCode", skip_serializing_if = "Option::is_none")]
906 pub inst_id_code: Option<u64>,
907 #[serde(rename = "tdMode")]
909 pub td_mode: OKXTradeMode,
910 pub side: OKXSide,
912 #[serde(rename = "ordType")]
914 pub ord_type: OKXAlgoOrderType,
915 #[serde(skip_serializing_if = "Option::is_none")]
917 pub sz: Option<String>,
918 #[serde(rename = "algoClOrdId", skip_serializing_if = "Option::is_none")]
920 pub algo_cl_ord_id: Option<String>,
921 #[serde(rename = "triggerPx", skip_serializing_if = "Option::is_none")]
923 pub trigger_px: Option<String>,
924 #[serde(rename = "orderPx", skip_serializing_if = "Option::is_none")]
926 pub order_px: Option<String>,
927 #[serde(rename = "triggerPxType", skip_serializing_if = "Option::is_none")]
929 pub trigger_px_type: Option<OKXTriggerType>,
930 #[serde(rename = "slTriggerPx", skip_serializing_if = "Option::is_none")]
932 pub sl_trigger_px: Option<String>,
933 #[serde(rename = "slOrdPx", skip_serializing_if = "Option::is_none")]
935 pub sl_ord_px: Option<String>,
936 #[serde(rename = "slTriggerPxType", skip_serializing_if = "Option::is_none")]
938 pub sl_trigger_px_type: Option<OKXTriggerType>,
939 #[serde(rename = "tpTriggerPx", skip_serializing_if = "Option::is_none")]
941 pub tp_trigger_px: Option<String>,
942 #[serde(rename = "tpOrdPx", skip_serializing_if = "Option::is_none")]
944 pub tp_ord_px: Option<String>,
945 #[serde(rename = "tpTriggerPxType", skip_serializing_if = "Option::is_none")]
947 pub tp_trigger_px_type: Option<OKXTriggerType>,
948 #[serde(rename = "tgtCcy", skip_serializing_if = "Option::is_none")]
950 pub tgt_ccy: Option<OKXTargetCurrency>,
951 #[serde(rename = "posSide", skip_serializing_if = "Option::is_none")]
953 pub pos_side: Option<OKXPositionSide>,
954 #[serde(rename = "closePosition", skip_serializing_if = "Option::is_none")]
956 pub close_position: Option<bool>,
957 #[serde(skip_serializing_if = "Option::is_none")]
959 pub tag: Option<String>,
960 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
962 pub reduce_only: Option<bool>,
963 #[serde(rename = "closeFraction", skip_serializing_if = "Option::is_none")]
965 pub close_fraction: Option<String>,
966 #[serde(rename = "callbackRatio", skip_serializing_if = "Option::is_none")]
969 pub callback_ratio: Option<String>,
970 #[serde(rename = "callbackSpread", skip_serializing_if = "Option::is_none")]
973 pub callback_spread: Option<String>,
974 #[serde(rename = "activePx", skip_serializing_if = "Option::is_none")]
977 pub active_px: Option<String>,
978}
979
980#[derive(Clone, Debug, Serialize, Deserialize)]
982#[serde(rename_all = "camelCase")]
983pub struct OKXPlaceAlgoOrderResponse {
984 pub algo_id: String,
986 #[serde(skip_serializing_if = "Option::is_none")]
988 pub algo_cl_ord_id: Option<String>,
989 #[serde(skip_serializing_if = "Option::is_none")]
991 pub s_code: Option<String>,
992 #[serde(skip_serializing_if = "Option::is_none")]
994 pub s_msg: Option<String>,
995 #[serde(skip_serializing_if = "Option::is_none")]
997 pub req_id: Option<String>,
998}
999
1000#[derive(Clone, Debug, Serialize, Deserialize)]
1002#[serde(rename_all = "camelCase")]
1003pub struct OKXCancelAlgoOrderRequest {
1004 pub inst_id: String,
1006 #[serde(rename = "instIdCode", skip_serializing_if = "Option::is_none")]
1008 pub inst_id_code: Option<u64>,
1009 #[serde(skip_serializing_if = "Option::is_none")]
1011 pub algo_id: Option<String>,
1012 #[serde(skip_serializing_if = "Option::is_none")]
1014 pub algo_cl_ord_id: Option<String>,
1015}
1016
1017#[derive(Clone, Debug, Serialize, Deserialize)]
1019#[serde(rename_all = "camelCase")]
1020pub struct OKXCancelAlgoOrderResponse {
1021 pub algo_id: String,
1023 #[serde(skip_serializing_if = "Option::is_none")]
1025 pub s_code: Option<String>,
1026 #[serde(skip_serializing_if = "Option::is_none")]
1028 pub s_msg: Option<String>,
1029}
1030
1031#[derive(Clone, Debug, Serialize, Deserialize)]
1033#[serde(rename_all = "camelCase")]
1034pub struct OKXAmendAlgoOrderRequest {
1035 pub inst_id: String,
1037 pub algo_id: String,
1039 #[serde(skip_serializing_if = "Option::is_none")]
1041 pub algo_cl_ord_id: Option<String>,
1042 #[serde(skip_serializing_if = "Option::is_none")]
1044 pub new_sz: Option<String>,
1045 #[serde(skip_serializing_if = "Option::is_none")]
1047 pub new_trigger_px: Option<String>,
1048 #[serde(skip_serializing_if = "Option::is_none")]
1050 pub new_order_px: Option<String>,
1051 #[serde(skip_serializing_if = "Option::is_none")]
1053 pub new_callback_ratio: Option<String>,
1054 #[serde(skip_serializing_if = "Option::is_none")]
1056 pub new_callback_spread: Option<String>,
1057 #[serde(skip_serializing_if = "Option::is_none")]
1059 pub new_active_px: Option<String>,
1060}
1061
1062#[derive(Clone, Debug, Serialize, Deserialize)]
1064#[serde(rename_all = "camelCase")]
1065pub struct OKXAmendAlgoOrderResponse {
1066 pub algo_id: String,
1068 #[serde(skip_serializing_if = "Option::is_none")]
1070 pub algo_cl_ord_id: Option<String>,
1071 #[serde(skip_serializing_if = "Option::is_none")]
1073 pub s_code: Option<String>,
1074 #[serde(skip_serializing_if = "Option::is_none")]
1076 pub s_msg: Option<String>,
1077 #[serde(skip_serializing_if = "Option::is_none")]
1079 pub req_id: Option<String>,
1080}
1081
1082#[derive(Clone, Debug, Serialize, Deserialize)]
1084#[serde(rename_all = "camelCase")]
1085pub struct OKXServerTime {
1086 #[serde(deserialize_with = "deserialize_string_to_u64")]
1088 pub ts: u64,
1089}
1090
1091#[derive(Clone, Debug, Serialize, Deserialize)]
1093#[serde(rename_all = "camelCase")]
1094pub struct OKXFeeRate {
1095 #[serde(deserialize_with = "crate::common::parse::deserialize_vip_level")]
1097 pub level: OKXVipLevel,
1098 pub taker: String,
1100 pub maker: String,
1102 pub taker_u: String,
1104 pub maker_u: String,
1106 #[serde(default)]
1108 pub delivery: String,
1109 #[serde(default)]
1111 pub exercise: String,
1112 pub inst_type: OKXInstrumentType,
1114 #[serde(default)]
1116 pub category: String,
1117 #[serde(deserialize_with = "deserialize_string_to_u64")]
1119 pub ts: u64,
1120}
1121
1122#[cfg(test)]
1123mod tests {
1124 use rstest::rstest;
1125 use serde_json;
1126
1127 use super::*;
1128
1129 #[rstest]
1130 fn test_algo_order_request_serialization() {
1131 let request = OKXPlaceAlgoOrderRequest {
1132 inst_id: "ETH-USDT-SWAP".to_string(),
1133 inst_id_code: None,
1134 td_mode: OKXTradeMode::Isolated,
1135 side: OKXSide::Buy,
1136 ord_type: OKXAlgoOrderType::Trigger,
1137 sz: Some("0.01".to_string()),
1138 algo_cl_ord_id: Some("test123".to_string()),
1139 trigger_px: Some("3000".to_string()),
1140 order_px: Some("-1".to_string()),
1141 trigger_px_type: Some(OKXTriggerType::Last),
1142 sl_trigger_px: None,
1143 sl_ord_px: None,
1144 sl_trigger_px_type: None,
1145 tp_trigger_px: None,
1146 tp_ord_px: None,
1147 tp_trigger_px_type: None,
1148 tgt_ccy: None,
1149 pos_side: None,
1150 close_position: None,
1151 tag: None,
1152 reduce_only: None,
1153 close_fraction: None,
1154 callback_ratio: None,
1155 callback_spread: None,
1156 active_px: None,
1157 };
1158
1159 let json = serde_json::to_string(&request).unwrap();
1160
1161 assert!(json.contains("\"instId\":\"ETH-USDT-SWAP\""));
1163 assert!(json.contains("\"tdMode\":\"isolated\""));
1164 assert!(json.contains("\"ordType\":\"trigger\""));
1165 assert!(json.contains("\"algoClOrdId\":\"test123\""));
1166 assert!(json.contains("\"triggerPx\":\"3000\""));
1167 assert!(json.contains("\"orderPx\":\"-1\""));
1168 assert!(json.contains("\"triggerPxType\":\"last\""));
1169
1170 assert!(!json.contains("tgtCcy"));
1172 assert!(!json.contains("posSide"));
1173 assert!(!json.contains("closePosition"));
1174 assert!(!json.contains("closeFraction"));
1175 }
1176
1177 #[rstest]
1178 fn test_algo_order_request_serializes_close_fraction() {
1179 let request = OKXPlaceAlgoOrderRequest {
1180 inst_id: "ETH-USDT-SWAP".to_string(),
1181 inst_id_code: None,
1182 td_mode: OKXTradeMode::Cross,
1183 side: OKXSide::Sell,
1184 ord_type: OKXAlgoOrderType::Conditional,
1185 sz: None,
1186 algo_cl_ord_id: Some("close-frac-123".to_string()),
1187 trigger_px: None,
1188 order_px: None,
1189 trigger_px_type: None,
1190 sl_trigger_px: Some("3000".to_string()),
1191 sl_ord_px: Some("-1".to_string()),
1192 sl_trigger_px_type: Some(OKXTriggerType::Last),
1193 tp_trigger_px: None,
1194 tp_ord_px: None,
1195 tp_trigger_px_type: None,
1196 tgt_ccy: None,
1197 pos_side: Some(OKXPositionSide::Net),
1198 close_position: None,
1199 tag: None,
1200 reduce_only: Some(true),
1201 close_fraction: Some("1".to_string()),
1202 callback_ratio: None,
1203 callback_spread: None,
1204 active_px: None,
1205 };
1206
1207 let json = serde_json::to_string(&request).unwrap();
1208
1209 assert!(json.contains("\"ordType\":\"conditional\""));
1210 assert!(json.contains("\"closeFraction\":\"1\""));
1211 assert!(json.contains("\"slTriggerPx\":\"3000\""));
1212 assert!(json.contains("\"slOrdPx\":\"-1\""));
1213 assert!(json.contains("\"slTriggerPxType\":\"last\""));
1214 assert!(json.contains("\"reduceOnly\":true"));
1215 assert!(!json.contains("\"sz\""));
1216 assert!(!json.contains("triggerPx"));
1217 }
1218
1219 #[rstest]
1220 fn test_algo_order_request_array_serialization() {
1221 let request = OKXPlaceAlgoOrderRequest {
1222 inst_id: "BTC-USDT".to_string(),
1223 inst_id_code: Some(10459),
1224 td_mode: OKXTradeMode::Cross,
1225 side: OKXSide::Sell,
1226 ord_type: OKXAlgoOrderType::Trigger,
1227 sz: Some("0.1".to_string()),
1228 algo_cl_ord_id: None,
1229 trigger_px: Some("50000".to_string()),
1230 order_px: Some("49900".to_string()),
1231 trigger_px_type: Some(OKXTriggerType::Mark),
1232 sl_trigger_px: None,
1233 sl_ord_px: None,
1234 sl_trigger_px_type: None,
1235 tp_trigger_px: None,
1236 tp_ord_px: None,
1237 tp_trigger_px_type: None,
1238 tgt_ccy: Some(OKXTargetCurrency::BaseCcy),
1239 pos_side: Some(OKXPositionSide::Net),
1240 close_position: None,
1241 tag: None,
1242 reduce_only: Some(true),
1243 close_fraction: None,
1244 callback_ratio: None,
1245 callback_spread: None,
1246 active_px: None,
1247 };
1248
1249 let json = serde_json::to_string(&[request]).unwrap();
1251
1252 assert!(json.starts_with('['));
1254 assert!(json.ends_with(']'));
1255
1256 assert!(json.contains("\"instId\":\"BTC-USDT\""));
1258 assert!(json.contains("\"tdMode\":\"cross\""));
1259 assert!(json.contains("\"triggerPx\":\"50000\""));
1260 assert!(json.contains("\"orderPx\":\"49900\""));
1261 assert!(json.contains("\"triggerPxType\":\"mark\""));
1262 assert!(json.contains("\"tgtCcy\":\"base_ccy\""));
1263 assert!(json.contains("\"posSide\":\"net\""));
1264 assert!(json.contains("\"reduceOnly\":true"));
1265 }
1266
1267 #[rstest]
1268 fn test_cancel_algo_order_request_serialization() {
1269 let request = OKXCancelAlgoOrderRequest {
1270 inst_id: "ETH-USDT-SWAP".to_string(),
1271 inst_id_code: None,
1272 algo_id: Some("123456".to_string()),
1273 algo_cl_ord_id: None,
1274 };
1275
1276 let json = serde_json::to_string(&request).unwrap();
1277
1278 assert!(json.contains("\"instId\":\"ETH-USDT-SWAP\""));
1280 assert!(json.contains("\"algoId\":\"123456\""));
1281 assert!(!json.contains("algoClOrdId"));
1282 }
1283
1284 #[rstest]
1285 fn test_cancel_algo_order_with_client_id_serialization() {
1286 let request = OKXCancelAlgoOrderRequest {
1287 inst_id: "BTC-USDT".to_string(),
1288 inst_id_code: Some(10459),
1289 algo_id: None,
1290 algo_cl_ord_id: Some("client123".to_string()),
1291 };
1292
1293 let json = serde_json::to_string(&[request]).unwrap();
1295
1296 assert!(json.starts_with('['));
1298 assert!(json.contains("\"instId\":\"BTC-USDT\""));
1299 assert!(json.contains("\"algoClOrdId\":\"client123\""));
1300 assert!(!json.contains("\"algoId\""));
1301 }
1302
1303 #[rstest]
1304 fn test_amend_algo_order_trigger_serialization() {
1305 let request = OKXAmendAlgoOrderRequest {
1306 inst_id: "ETH-USDT-SWAP".to_string(),
1307 algo_id: "123456".to_string(),
1308 algo_cl_ord_id: None,
1309 new_sz: None,
1310 new_trigger_px: Some("3500".to_string()),
1311 new_order_px: Some("3490".to_string()),
1312 new_callback_ratio: None,
1313 new_callback_spread: None,
1314 new_active_px: None,
1315 };
1316
1317 let json = serde_json::to_string(&request).unwrap();
1318
1319 assert!(json.contains("\"instId\":\"ETH-USDT-SWAP\""));
1320 assert!(json.contains("\"algoId\":\"123456\""));
1321 assert!(json.contains("\"newTriggerPx\":\"3500\""));
1322 assert!(json.contains("\"newOrderPx\":\"3490\""));
1323 assert!(!json.contains("newSz"));
1324 assert!(!json.contains("algoClOrdId"));
1325 assert!(!json.contains("newCallbackRatio"));
1326 }
1327
1328 #[rstest]
1329 fn test_amend_algo_order_trailing_stop_serialization() {
1330 let request = OKXAmendAlgoOrderRequest {
1331 inst_id: "BTC-USDT-SWAP".to_string(),
1332 algo_id: "789012".to_string(),
1333 algo_cl_ord_id: Some("client456".to_string()),
1334 new_sz: Some("0.1".to_string()),
1335 new_trigger_px: None,
1336 new_order_px: None,
1337 new_callback_ratio: Some("0.02".to_string()),
1338 new_callback_spread: None,
1339 new_active_px: Some("50000".to_string()),
1340 };
1341
1342 let json = serde_json::to_string(&request).unwrap();
1343
1344 assert!(json.contains("\"instId\":\"BTC-USDT-SWAP\""));
1345 assert!(json.contains("\"algoId\":\"789012\""));
1346 assert!(json.contains("\"algoClOrdId\":\"client456\""));
1347 assert!(json.contains("\"newSz\":\"0.1\""));
1348 assert!(json.contains("\"newCallbackRatio\":\"0.02\""));
1349 assert!(json.contains("\"newActivePx\":\"50000\""));
1350 assert!(!json.contains("newTriggerPx"));
1351 assert!(!json.contains("newOrderPx"));
1352 }
1353
1354 #[rstest]
1355 fn test_trailing_stop_request_callback_ratio_serialization() {
1356 let request = OKXPlaceAlgoOrderRequest {
1357 inst_id: "BTC-USDT-SWAP".to_string(),
1358 inst_id_code: None,
1359 td_mode: OKXTradeMode::Cross,
1360 side: OKXSide::Buy,
1361 ord_type: OKXAlgoOrderType::MoveOrderStop,
1362 sz: Some("0.1".to_string()),
1363 algo_cl_ord_id: Some("trail-001".to_string()),
1364 trigger_px: None,
1365 order_px: None,
1366 trigger_px_type: None,
1367 sl_trigger_px: None,
1368 sl_ord_px: None,
1369 sl_trigger_px_type: None,
1370 tp_trigger_px: None,
1371 tp_ord_px: None,
1372 tp_trigger_px_type: None,
1373 tgt_ccy: None,
1374 pos_side: None,
1375 close_position: None,
1376 tag: None,
1377 reduce_only: None,
1378 close_fraction: None,
1379 callback_ratio: Some("0.01".to_string()),
1380 callback_spread: None,
1381 active_px: None,
1382 };
1383
1384 let json = serde_json::to_string(&request).unwrap();
1385
1386 assert!(json.contains("\"ordType\":\"move_order_stop\""));
1387 assert!(json.contains("\"callbackRatio\":\"0.01\""));
1388 assert!(!json.contains("callbackSpread"));
1389 assert!(!json.contains("activePx"));
1390 }
1391
1392 #[rstest]
1393 fn test_trailing_stop_request_callback_spread_serialization() {
1394 let request = OKXPlaceAlgoOrderRequest {
1395 inst_id: "ETH-USDT-SWAP".to_string(),
1396 inst_id_code: None,
1397 td_mode: OKXTradeMode::Isolated,
1398 side: OKXSide::Sell,
1399 ord_type: OKXAlgoOrderType::MoveOrderStop,
1400 sz: Some("1.0".to_string()),
1401 algo_cl_ord_id: None,
1402 trigger_px: None,
1403 order_px: None,
1404 trigger_px_type: None,
1405 sl_trigger_px: None,
1406 sl_ord_px: None,
1407 sl_trigger_px_type: None,
1408 tp_trigger_px: None,
1409 tp_ord_px: None,
1410 tp_trigger_px_type: None,
1411 tgt_ccy: None,
1412 pos_side: None,
1413 close_position: None,
1414 tag: None,
1415 reduce_only: Some(true),
1416 close_fraction: None,
1417 callback_ratio: None,
1418 callback_spread: Some("50.5".to_string()),
1419 active_px: None,
1420 };
1421
1422 let json = serde_json::to_string(&request).unwrap();
1423
1424 assert!(json.contains("\"callbackSpread\":\"50.5\""));
1425 assert!(!json.contains("callbackRatio"));
1426 assert!(!json.contains("activePx"));
1427 }
1428
1429 #[rstest]
1430 fn test_trailing_stop_request_with_activation_price_serialization() {
1431 let request = OKXPlaceAlgoOrderRequest {
1432 inst_id: "BTC-USDT-SWAP".to_string(),
1433 inst_id_code: None,
1434 td_mode: OKXTradeMode::Cross,
1435 side: OKXSide::Buy,
1436 ord_type: OKXAlgoOrderType::MoveOrderStop,
1437 sz: Some("0.5".to_string()),
1438 algo_cl_ord_id: None,
1439 trigger_px: None,
1440 order_px: None,
1441 trigger_px_type: None,
1442 sl_trigger_px: None,
1443 sl_ord_px: None,
1444 sl_trigger_px_type: None,
1445 tp_trigger_px: None,
1446 tp_ord_px: None,
1447 tp_trigger_px_type: None,
1448 tgt_ccy: None,
1449 pos_side: None,
1450 close_position: None,
1451 tag: None,
1452 reduce_only: None,
1453 close_fraction: None,
1454 callback_ratio: Some("0.005".to_string()),
1455 callback_spread: None,
1456 active_px: Some("65000".to_string()),
1457 };
1458
1459 let json = serde_json::to_string(&request).unwrap();
1460
1461 assert!(json.contains("\"callbackRatio\":\"0.005\""));
1462 assert!(json.contains("\"activePx\":\"65000\""));
1463 assert!(!json.contains("callbackSpread"));
1464 }
1465
1466 #[rstest]
1467 fn test_amend_algo_order_callback_spread_serialization() {
1468 let request = OKXAmendAlgoOrderRequest {
1469 inst_id: "ETH-USDT-SWAP".to_string(),
1470 algo_id: "456789".to_string(),
1471 algo_cl_ord_id: None,
1472 new_sz: None,
1473 new_trigger_px: None,
1474 new_order_px: None,
1475 new_callback_ratio: None,
1476 new_callback_spread: Some("25.0".to_string()),
1477 new_active_px: Some("4000".to_string()),
1478 };
1479
1480 let json = serde_json::to_string(&request).unwrap();
1481
1482 assert!(json.contains("\"newCallbackSpread\":\"25.0\""));
1483 assert!(json.contains("\"newActivePx\":\"4000\""));
1484 assert!(!json.contains("newCallbackRatio"));
1485 assert!(!json.contains("newTriggerPx"));
1486 assert!(!json.contains("newSz"));
1487 }
1488
1489 #[rstest]
1490 fn test_amend_algo_order_size_only_serialization() {
1491 let request = OKXAmendAlgoOrderRequest {
1492 inst_id: "BTC-USDT-SWAP".to_string(),
1493 algo_id: "111222".to_string(),
1494 algo_cl_ord_id: None,
1495 new_sz: Some("0.5".to_string()),
1496 new_trigger_px: None,
1497 new_order_px: None,
1498 new_callback_ratio: None,
1499 new_callback_spread: None,
1500 new_active_px: None,
1501 };
1502
1503 let json = serde_json::to_string(&request).unwrap();
1504
1505 assert!(json.contains("\"newSz\":\"0.5\""));
1506 assert!(!json.contains("newTriggerPx"));
1507 assert!(!json.contains("newOrderPx"));
1508 assert!(!json.contains("newCallbackRatio"));
1509 assert!(!json.contains("newCallbackSpread"));
1510 assert!(!json.contains("newActivePx"));
1511 }
1512
1513 #[rstest]
1514 fn test_amend_algo_order_all_fields_serialization() {
1515 let request = OKXAmendAlgoOrderRequest {
1516 inst_id: "BTC-USDT-SWAP".to_string(),
1517 algo_id: "333444".to_string(),
1518 algo_cl_ord_id: Some("client789".to_string()),
1519 new_sz: Some("1.0".to_string()),
1520 new_trigger_px: Some("60000".to_string()),
1521 new_order_px: Some("59900".to_string()),
1522 new_callback_ratio: Some("0.015".to_string()),
1523 new_callback_spread: Some("100".to_string()),
1524 new_active_px: Some("62000".to_string()),
1525 };
1526
1527 let json = serde_json::to_string(&request).unwrap();
1528
1529 assert!(json.contains("\"instId\":\"BTC-USDT-SWAP\""));
1530 assert!(json.contains("\"algoId\":\"333444\""));
1531 assert!(json.contains("\"algoClOrdId\":\"client789\""));
1532 assert!(json.contains("\"newSz\":\"1.0\""));
1533 assert!(json.contains("\"newTriggerPx\":\"60000\""));
1534 assert!(json.contains("\"newOrderPx\":\"59900\""));
1535 assert!(json.contains("\"newCallbackRatio\":\"0.015\""));
1536 assert!(json.contains("\"newCallbackSpread\":\"100\""));
1537 assert!(json.contains("\"newActivePx\":\"62000\""));
1538 }
1539
1540 #[rstest]
1541 fn test_place_order_request_serializes_px_usd() {
1542 let request = OKXPlaceOrderRequest {
1543 inst_id: "BTC-USD-250328-50000-C".to_string(),
1544 td_mode: OKXTradeMode::Cross,
1545 ccy: None,
1546 cl_ord_id: Some("test-opt-1".to_string()),
1547 tag: None,
1548 side: OKXSide::Buy,
1549 pos_side: Some(OKXPositionSide::Net),
1550 ord_type: OKXOrderType::Limit,
1551 sz: "1".to_string(),
1552 px: None,
1553 px_usd: Some("100.5".to_string()),
1554 px_vol: None,
1555 reduce_only: None,
1556 tgt_ccy: None,
1557 attach_algo_ords: None,
1558 };
1559
1560 let json = serde_json::to_string(&request).unwrap();
1561 assert!(json.contains("\"pxUsd\":\"100.5\""));
1562 assert!(!json.contains("\"pxVol\""));
1563 assert!(!json.contains("\"px\":"));
1564 }
1565
1566 #[rstest]
1567 fn test_place_order_request_serializes_px_vol() {
1568 let request = OKXPlaceOrderRequest {
1569 inst_id: "BTC-USD-250328-50000-C".to_string(),
1570 td_mode: OKXTradeMode::Cross,
1571 ccy: None,
1572 cl_ord_id: Some("test-opt-2".to_string()),
1573 tag: None,
1574 side: OKXSide::Buy,
1575 pos_side: Some(OKXPositionSide::Net),
1576 ord_type: OKXOrderType::Limit,
1577 sz: "1".to_string(),
1578 px: None,
1579 px_usd: None,
1580 px_vol: Some("0.55".to_string()),
1581 reduce_only: None,
1582 tgt_ccy: None,
1583 attach_algo_ords: None,
1584 };
1585
1586 let json = serde_json::to_string(&request).unwrap();
1587 assert!(json.contains("\"pxVol\":\"0.55\""));
1588 assert!(!json.contains("\"pxUsd\""));
1589 assert!(!json.contains("\"px\":"));
1590 }
1591}