1use nautilus_model::{
19 data::BarSpecification,
20 enums::{
21 BarAggregation, LiquiditySide, MarketStatusAction, OrderSide, OrderStatus, OrderType,
22 PositionSide,
23 },
24};
25use serde::{Deserialize, Serialize};
26use strum::{AsRefStr, Display, EnumIter, EnumString, IntoStaticStr};
27
28use crate::{error::DydxError, grpc::types::ChainId};
29
30#[derive(
32 Copy,
33 Clone,
34 Debug,
35 Display,
36 PartialEq,
37 Eq,
38 Hash,
39 AsRefStr,
40 EnumIter,
41 EnumString,
42 Serialize,
43 Deserialize,
44)]
45#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
46pub enum DydxOrderStatus {
47 Open,
49 Filled,
51 Canceled,
53 BestEffortCanceled,
55 PartiallyFilled,
57 BestEffortOpened,
59 Untriggered,
61}
62
63impl From<DydxOrderStatus> for OrderStatus {
64 fn from(value: DydxOrderStatus) -> Self {
65 match value {
66 DydxOrderStatus::Open | DydxOrderStatus::BestEffortOpened => Self::Accepted,
67 DydxOrderStatus::PartiallyFilled => Self::PartiallyFilled,
68 DydxOrderStatus::Filled => Self::Filled,
69 DydxOrderStatus::Canceled | DydxOrderStatus::BestEffortCanceled => Self::Canceled,
70 DydxOrderStatus::Untriggered => Self::PendingUpdate,
71 }
72 }
73}
74
75#[derive(
77 Copy,
78 Clone,
79 Debug,
80 Display,
81 PartialEq,
82 Eq,
83 Hash,
84 AsRefStr,
85 EnumIter,
86 EnumString,
87 Serialize,
88 Deserialize,
89)]
90#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
91pub enum DydxTimeInForce {
92 Gtt,
94 Fok,
96 Ioc,
98}
99
100#[derive(
102 Copy,
103 Clone,
104 Debug,
105 Display,
106 PartialEq,
107 Eq,
108 Hash,
109 AsRefStr,
110 EnumIter,
111 EnumString,
112 Serialize,
113 Deserialize,
114)]
115#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
116#[cfg_attr(
117 feature = "python",
118 pyo3::pyclass(
119 module = "nautilus_trader.core.nautilus_pyo3.dydx",
120 eq,
121 eq_int,
122 from_py_object
123 )
124)]
125#[cfg_attr(
126 feature = "python",
127 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.dydx")
128)]
129pub enum DydxOrderSide {
130 Buy,
132 Sell,
134}
135
136impl TryFrom<OrderSide> for DydxOrderSide {
137 type Error = DydxError;
138
139 fn try_from(value: OrderSide) -> Result<Self, Self::Error> {
140 match value {
141 OrderSide::Buy => Ok(Self::Buy),
142 OrderSide::Sell => Ok(Self::Sell),
143 _ => Err(DydxError::InvalidOrderSide(format!("{value:?}"))),
144 }
145 }
146}
147
148impl DydxOrderSide {
149 pub fn try_from_order_side(value: OrderSide) -> anyhow::Result<Self> {
155 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
156 }
157}
158
159impl From<DydxOrderSide> for OrderSide {
160 fn from(side: DydxOrderSide) -> Self {
161 match side {
162 DydxOrderSide::Buy => Self::Buy,
163 DydxOrderSide::Sell => Self::Sell,
164 }
165 }
166}
167
168#[derive(
170 Copy,
171 Clone,
172 Debug,
173 Display,
174 PartialEq,
175 Eq,
176 Hash,
177 AsRefStr,
178 EnumIter,
179 EnumString,
180 Serialize,
181 Deserialize,
182)]
183#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
184#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
185#[cfg_attr(
186 feature = "python",
187 pyo3::pyclass(
188 module = "nautilus_trader.core.nautilus_pyo3.dydx",
189 eq,
190 eq_int,
191 from_py_object
192 )
193)]
194#[cfg_attr(
195 feature = "python",
196 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.dydx")
197)]
198pub enum DydxOrderType {
199 Limit,
201 Market,
203 StopLimit,
205 StopMarket,
207 #[serde(rename = "TAKE_PROFIT", alias = "TAKE_PROFIT_LIMIT")]
209 #[strum(serialize = "TAKE_PROFIT", serialize = "TAKE_PROFIT_LIMIT")]
210 TakeProfitLimit,
211 TakeProfitMarket,
213 TrailingStop,
215}
216
217impl TryFrom<OrderType> for DydxOrderType {
218 type Error = DydxError;
219
220 fn try_from(value: OrderType) -> Result<Self, Self::Error> {
221 match value {
222 OrderType::Market => Ok(Self::Market),
223 OrderType::Limit => Ok(Self::Limit),
224 OrderType::StopMarket => Ok(Self::StopMarket),
225 OrderType::StopLimit => Ok(Self::StopLimit),
226 OrderType::MarketIfTouched => Ok(Self::TakeProfitMarket),
227 OrderType::LimitIfTouched => Ok(Self::TakeProfitLimit),
228 OrderType::TrailingStopMarket | OrderType::TrailingStopLimit => Ok(Self::TrailingStop),
229 OrderType::MarketToLimit => Err(DydxError::UnsupportedOrderType(format!("{value:?}"))),
230 }
231 }
232}
233
234impl DydxOrderType {
235 pub fn try_from_order_type(value: OrderType) -> anyhow::Result<Self> {
241 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
242 }
243
244 #[must_use]
246 pub const fn is_conditional(&self) -> bool {
247 matches!(
248 self,
249 Self::StopLimit
250 | Self::StopMarket
251 | Self::TakeProfitLimit
252 | Self::TakeProfitMarket
253 | Self::TrailingStop
254 )
255 }
256
257 #[must_use]
259 pub const fn condition_type(&self) -> DydxConditionType {
260 match self {
261 Self::StopLimit | Self::StopMarket => DydxConditionType::StopLoss,
262 Self::TakeProfitLimit | Self::TakeProfitMarket => DydxConditionType::TakeProfit,
263 _ => DydxConditionType::Unspecified,
264 }
265 }
266
267 #[must_use]
269 pub const fn is_market_execution(&self) -> bool {
270 matches!(
271 self,
272 Self::Market | Self::StopMarket | Self::TakeProfitMarket
273 )
274 }
275}
276
277impl From<DydxOrderType> for OrderType {
278 fn from(value: DydxOrderType) -> Self {
279 match value {
280 DydxOrderType::Market => Self::Market,
281 DydxOrderType::Limit => Self::Limit,
282 DydxOrderType::StopMarket => Self::StopMarket,
283 DydxOrderType::StopLimit => Self::StopLimit,
284 DydxOrderType::TakeProfitMarket => Self::MarketIfTouched,
285 DydxOrderType::TakeProfitLimit => Self::LimitIfTouched,
286 DydxOrderType::TrailingStop => Self::TrailingStopMarket,
287 }
288 }
289}
290
291#[derive(
293 Copy,
294 Clone,
295 Debug,
296 Display,
297 PartialEq,
298 Eq,
299 Hash,
300 AsRefStr,
301 EnumIter,
302 EnumString,
303 Serialize,
304 Deserialize,
305)]
306#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
307pub enum DydxOrderExecution {
308 Default,
310 Ioc,
312 Fok,
314 PostOnly,
316}
317
318#[derive(
320 Copy, Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumIter, Serialize, Deserialize,
321)]
322pub enum DydxOrderFlags {
323 ShortTerm = 0,
325 Conditional = 32,
327 LongTerm = 64,
329}
330
331#[derive(
337 Copy,
338 Clone,
339 Debug,
340 Display,
341 PartialEq,
342 Eq,
343 Hash,
344 AsRefStr,
345 EnumIter,
346 EnumString,
347 Serialize,
348 Deserialize,
349)]
350#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
351pub enum DydxConditionType {
352 Unspecified,
354 StopLoss,
356 TakeProfit,
358}
359
360#[derive(
362 Copy,
363 Clone,
364 Debug,
365 Display,
366 PartialEq,
367 Eq,
368 Hash,
369 AsRefStr,
370 EnumIter,
371 EnumString,
372 Serialize,
373 Deserialize,
374)]
375#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
376pub enum DydxPositionSide {
377 Long,
379 Short,
381}
382
383impl From<DydxPositionSide> for PositionSide {
384 fn from(value: DydxPositionSide) -> Self {
385 match value {
386 DydxPositionSide::Long => Self::Long,
387 DydxPositionSide::Short => Self::Short,
388 }
389 }
390}
391
392#[derive(
394 Copy,
395 Clone,
396 Debug,
397 Display,
398 PartialEq,
399 Eq,
400 Hash,
401 AsRefStr,
402 EnumIter,
403 EnumString,
404 Serialize,
405 Deserialize,
406)]
407#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
408pub enum DydxPositionStatus {
409 Open,
411 Closed,
413 Liquidated,
415}
416
417impl DydxPositionStatus {
418 #[must_use]
420 pub const fn is_closed(&self) -> bool {
421 matches!(self, Self::Closed | Self::Liquidated)
422 }
423}
424
425#[derive(
427 Copy,
428 Clone,
429 Debug,
430 Display,
431 PartialEq,
432 Eq,
433 Hash,
434 AsRefStr,
435 EnumIter,
436 EnumString,
437 Serialize,
438 Deserialize,
439)]
440#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
441pub enum DydxMarketStatus {
442 Active,
444 Paused,
446 CancelOnly,
448 PostOnly,
450 Initializing,
452 FinalSettlement,
454 #[serde(other)]
456 Unknown,
457}
458
459impl From<DydxMarketStatus> for MarketStatusAction {
460 fn from(value: DydxMarketStatus) -> Self {
461 match value {
462 DydxMarketStatus::Active => Self::Trading,
463 DydxMarketStatus::Paused => Self::Pause,
464 DydxMarketStatus::CancelOnly => Self::Halt,
465 DydxMarketStatus::PostOnly => Self::Quoting,
466 DydxMarketStatus::Initializing => Self::PreOpen,
467 DydxMarketStatus::FinalSettlement => Self::Close,
468 DydxMarketStatus::Unknown => Self::None,
470 }
471 }
472}
473
474#[derive(
476 Copy,
477 Clone,
478 Debug,
479 Display,
480 PartialEq,
481 Eq,
482 Hash,
483 AsRefStr,
484 EnumIter,
485 EnumString,
486 Serialize,
487 Deserialize,
488)]
489#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
490pub enum DydxFillType {
491 Limit,
493 Liquidated,
495 Liquidation,
497 Deleveraged,
499 Offsetting,
501 #[serde(other)]
503 Unknown,
504}
505
506#[derive(
508 Copy,
509 Clone,
510 Debug,
511 Display,
512 PartialEq,
513 Eq,
514 Hash,
515 AsRefStr,
516 EnumIter,
517 EnumString,
518 Serialize,
519 Deserialize,
520)]
521#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
522pub enum DydxLiquidity {
523 Maker,
525 Taker,
527}
528
529impl From<DydxLiquidity> for LiquiditySide {
530 fn from(value: DydxLiquidity) -> Self {
531 match value {
532 DydxLiquidity::Maker => Self::Maker,
533 DydxLiquidity::Taker => Self::Taker,
534 }
535 }
536}
537
538impl From<LiquiditySide> for DydxLiquidity {
539 fn from(value: LiquiditySide) -> Self {
540 match value {
541 LiquiditySide::Maker => Self::Maker,
542 LiquiditySide::Taker => Self::Taker,
543 LiquiditySide::NoLiquiditySide => Self::Taker, }
545 }
546}
547
548#[derive(
550 Copy,
551 Clone,
552 Debug,
553 Display,
554 PartialEq,
555 Eq,
556 Hash,
557 AsRefStr,
558 EnumIter,
559 EnumString,
560 Serialize,
561 Deserialize,
562)]
563#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
564pub enum DydxTickerType {
565 Perpetual,
567 #[serde(other)]
569 Unknown,
570}
571
572#[derive(
576 Copy,
577 Clone,
578 Debug,
579 Display,
580 PartialEq,
581 Eq,
582 Hash,
583 AsRefStr,
584 EnumIter,
585 EnumString,
586 Serialize,
587 Deserialize,
588)]
589#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
590pub enum DydxTradeType {
591 Limit,
593 Market,
595 Liquidated,
597 TwapSuborder,
599 StopLimit,
601 TakeProfitLimit,
603 #[serde(other)]
605 Unknown,
606}
607
608#[derive(
610 Copy,
611 Clone,
612 Debug,
613 Display,
614 PartialEq,
615 Eq,
616 Hash,
617 AsRefStr,
618 EnumIter,
619 EnumString,
620 Serialize,
621 Deserialize,
622)]
623#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
624#[cfg_attr(
625 feature = "python",
626 pyo3::pyclass(
627 module = "nautilus_trader.core.nautilus_pyo3.dydx",
628 eq,
629 eq_int,
630 from_py_object
631 )
632)]
633#[cfg_attr(
634 feature = "python",
635 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.dydx")
636)]
637pub enum DydxTransferType {
638 TransferIn,
640 TransferOut,
642 Deposit,
644 Withdrawal,
646}
647
648#[derive(
650 Copy,
651 Clone,
652 Debug,
653 Display,
654 PartialEq,
655 Eq,
656 Hash,
657 AsRefStr,
658 IntoStaticStr,
659 EnumIter,
660 EnumString,
661 Serialize,
662 Deserialize,
663)]
664#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
665#[derive(Default)]
666#[cfg_attr(
667 feature = "python",
668 pyo3::pyclass(
669 module = "nautilus_trader.core.nautilus_pyo3.dydx",
670 eq,
671 eq_int,
672 from_py_object
673 )
674)]
675#[cfg_attr(
676 feature = "python",
677 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.dydx")
678)]
679pub enum DydxCandleResolution {
680 #[serde(rename = "1MIN")]
682 #[strum(serialize = "1MIN")]
683 #[default]
684 OneMinute,
685 #[serde(rename = "5MINS")]
687 #[strum(serialize = "5MINS")]
688 FiveMinutes,
689 #[serde(rename = "15MINS")]
691 #[strum(serialize = "15MINS")]
692 FifteenMinutes,
693 #[serde(rename = "30MINS")]
695 #[strum(serialize = "30MINS")]
696 ThirtyMinutes,
697 #[serde(rename = "1HOUR")]
699 #[strum(serialize = "1HOUR")]
700 OneHour,
701 #[serde(rename = "4HOURS")]
703 #[strum(serialize = "4HOURS")]
704 FourHours,
705 #[serde(rename = "1DAY")]
707 #[strum(serialize = "1DAY")]
708 OneDay,
709}
710
711impl DydxCandleResolution {
712 pub fn from_bar_spec(spec: &BarSpecification) -> anyhow::Result<Self> {
718 match spec.step.get() {
719 1 => match spec.aggregation {
720 BarAggregation::Minute => Ok(Self::OneMinute),
721 BarAggregation::Hour => Ok(Self::OneHour),
722 BarAggregation::Day => Ok(Self::OneDay),
723 _ => anyhow::bail!("Unsupported bar aggregation: {:?}", spec.aggregation),
724 },
725 5 if spec.aggregation == BarAggregation::Minute => Ok(Self::FiveMinutes),
726 15 if spec.aggregation == BarAggregation::Minute => Ok(Self::FifteenMinutes),
727 30 if spec.aggregation == BarAggregation::Minute => Ok(Self::ThirtyMinutes),
728 4 if spec.aggregation == BarAggregation::Hour => Ok(Self::FourHours),
729 step => anyhow::bail!(
730 "Unsupported bar step: {step} with aggregation {:?}",
731 spec.aggregation
732 ),
733 }
734 }
735}
736
737#[derive(
741 Copy,
742 Clone,
743 Debug,
744 Default,
745 Display,
746 PartialEq,
747 Eq,
748 Hash,
749 AsRefStr,
750 EnumIter,
751 EnumString,
752 Serialize,
753 Deserialize,
754)]
755#[strum(serialize_all = "lowercase")]
756#[serde(rename_all = "lowercase")]
757#[cfg_attr(
758 feature = "python",
759 pyo3::pyclass(
760 eq,
761 eq_int,
762 module = "nautilus_trader.core.nautilus_pyo3.dydx",
763 from_py_object,
764 rename_all = "SCREAMING_SNAKE_CASE",
765 )
766)]
767#[cfg_attr(
768 feature = "python",
769 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.dydx")
770)]
771pub enum DydxNetwork {
772 #[default]
774 Mainnet,
775 Testnet,
777}
778
779impl DydxNetwork {
780 #[must_use]
782 pub const fn chain_id(self) -> ChainId {
783 match self {
784 Self::Mainnet => ChainId::Mainnet1,
785 Self::Testnet => ChainId::Testnet4,
786 }
787 }
788
789 #[must_use]
791 pub const fn as_str(self) -> &'static str {
792 match self {
793 Self::Mainnet => "mainnet",
794 Self::Testnet => "testnet",
795 }
796 }
797}
798
799#[cfg(test)]
800mod tests {
801 use rstest::rstest;
802
803 use super::*;
804
805 #[rstest]
806 fn test_reference_enums_tolerate_unmodeled_values() {
807 let status: DydxMarketStatus = serde_json::from_str("\"SOME_NEW_STATUS\"").unwrap();
810 let ticker: DydxTickerType = serde_json::from_str("\"SPOT\"").unwrap();
811 let trade: DydxTradeType = serde_json::from_str("\"SOME_NEW_TRADE\"").unwrap();
812 let fill: DydxFillType = serde_json::from_str("\"SOME_NEW_FILL\"").unwrap();
813 assert_eq!(status, DydxMarketStatus::Unknown);
814 assert_eq!(ticker, DydxTickerType::Unknown);
815 assert_eq!(trade, DydxTradeType::Unknown);
816 assert_eq!(fill, DydxFillType::Unknown);
817 assert_eq!(
818 MarketStatusAction::from(DydxMarketStatus::Unknown),
819 MarketStatusAction::None
820 );
821 }
822
823 #[rstest]
824 fn test_order_status_conversion() {
825 assert_eq!(
826 OrderStatus::from(DydxOrderStatus::Open),
827 OrderStatus::Accepted
828 );
829 assert_eq!(
830 OrderStatus::from(DydxOrderStatus::Filled),
831 OrderStatus::Filled
832 );
833 assert_eq!(
834 OrderStatus::from(DydxOrderStatus::Canceled),
835 OrderStatus::Canceled
836 );
837 }
838
839 #[rstest]
840 fn test_liquidity_conversion() {
841 assert_eq!(
842 LiquiditySide::from(DydxLiquidity::Maker),
843 LiquiditySide::Maker
844 );
845 assert_eq!(
846 LiquiditySide::from(DydxLiquidity::Taker),
847 LiquiditySide::Taker
848 );
849 }
850
851 #[rstest]
852 fn test_order_type_is_conditional() {
853 assert!(DydxOrderType::StopLimit.is_conditional());
854 assert!(DydxOrderType::StopMarket.is_conditional());
855 assert!(DydxOrderType::TakeProfitLimit.is_conditional());
856 assert!(DydxOrderType::TakeProfitMarket.is_conditional());
857 assert!(DydxOrderType::TrailingStop.is_conditional());
858 assert!(!DydxOrderType::Limit.is_conditional());
859 assert!(!DydxOrderType::Market.is_conditional());
860 }
861
862 #[rstest]
863 fn test_condition_type_mapping() {
864 assert_eq!(
865 DydxOrderType::StopLimit.condition_type(),
866 DydxConditionType::StopLoss
867 );
868 assert_eq!(
869 DydxOrderType::StopMarket.condition_type(),
870 DydxConditionType::StopLoss
871 );
872 assert_eq!(
873 DydxOrderType::TakeProfitLimit.condition_type(),
874 DydxConditionType::TakeProfit
875 );
876 assert_eq!(
877 DydxOrderType::TakeProfitMarket.condition_type(),
878 DydxConditionType::TakeProfit
879 );
880 assert_eq!(
881 DydxOrderType::Limit.condition_type(),
882 DydxConditionType::Unspecified
883 );
884 }
885
886 #[rstest]
887 fn test_is_market_execution() {
888 assert!(DydxOrderType::Market.is_market_execution());
889 assert!(DydxOrderType::StopMarket.is_market_execution());
890 assert!(DydxOrderType::TakeProfitMarket.is_market_execution());
891 assert!(!DydxOrderType::Limit.is_market_execution());
892 assert!(!DydxOrderType::StopLimit.is_market_execution());
893 assert!(!DydxOrderType::TakeProfitLimit.is_market_execution());
894 }
895
896 #[rstest]
897 fn test_order_type_to_nautilus() {
898 assert_eq!(OrderType::from(DydxOrderType::Market), OrderType::Market);
899 assert_eq!(OrderType::from(DydxOrderType::Limit), OrderType::Limit);
900 assert_eq!(
901 OrderType::from(DydxOrderType::StopMarket),
902 OrderType::StopMarket
903 );
904 assert_eq!(
905 OrderType::from(DydxOrderType::StopLimit),
906 OrderType::StopLimit
907 );
908 }
909
910 #[rstest]
911 fn test_order_side_conversion_from_nautilus() {
912 assert_eq!(
913 DydxOrderSide::try_from(OrderSide::Buy).unwrap(),
914 DydxOrderSide::Buy
915 );
916 assert_eq!(
917 DydxOrderSide::try_from(OrderSide::Sell).unwrap(),
918 DydxOrderSide::Sell
919 );
920 assert!(DydxOrderSide::try_from(OrderSide::NoOrderSide).is_err());
921 }
922
923 #[rstest]
924 fn test_order_side_conversion_to_nautilus() {
925 assert_eq!(OrderSide::from(DydxOrderSide::Buy), OrderSide::Buy);
926 assert_eq!(OrderSide::from(DydxOrderSide::Sell), OrderSide::Sell);
927 }
928
929 #[rstest]
930 fn test_order_type_conversion_from_nautilus() {
931 assert_eq!(
932 DydxOrderType::try_from(OrderType::Market).unwrap(),
933 DydxOrderType::Market
934 );
935 assert_eq!(
936 DydxOrderType::try_from(OrderType::Limit).unwrap(),
937 DydxOrderType::Limit
938 );
939 assert_eq!(
940 DydxOrderType::try_from(OrderType::StopMarket).unwrap(),
941 DydxOrderType::StopMarket
942 );
943 assert_eq!(
944 DydxOrderType::try_from(OrderType::StopLimit).unwrap(),
945 DydxOrderType::StopLimit
946 );
947 assert!(DydxOrderType::try_from(OrderType::MarketToLimit).is_err());
948 }
949
950 #[rstest]
951 fn test_order_type_conversion_to_nautilus() {
952 assert_eq!(OrderType::from(DydxOrderType::Market), OrderType::Market);
953 assert_eq!(OrderType::from(DydxOrderType::Limit), OrderType::Limit);
954 assert_eq!(
955 OrderType::from(DydxOrderType::StopMarket),
956 OrderType::StopMarket
957 );
958 assert_eq!(
959 OrderType::from(DydxOrderType::StopLimit),
960 OrderType::StopLimit
961 );
962 }
963
964 #[rstest]
968 #[case("\"TAKE_PROFIT\"", DydxOrderType::TakeProfitLimit)]
969 #[case("\"TAKE_PROFIT_LIMIT\"", DydxOrderType::TakeProfitLimit)]
970 #[case("\"TAKE_PROFIT_MARKET\"", DydxOrderType::TakeProfitMarket)]
971 fn test_dydx_order_type_take_profit_serde(
972 #[case] input: &str,
973 #[case] expected: DydxOrderType,
974 ) {
975 let parsed: DydxOrderType = serde_json::from_str(input).unwrap();
976 assert_eq!(parsed, expected);
977 }
978
979 #[rstest]
980 fn test_dydx_network_chain_id_mapping() {
981 assert_eq!(DydxNetwork::Mainnet.chain_id(), ChainId::Mainnet1);
983 assert_eq!(DydxNetwork::Testnet.chain_id(), ChainId::Testnet4);
984 }
985
986 #[rstest]
987 fn test_dydx_network_as_str() {
988 assert_eq!(DydxNetwork::Mainnet.as_str(), "mainnet");
990 assert_eq!(DydxNetwork::Testnet.as_str(), "testnet");
991 }
992
993 #[rstest]
994 fn test_dydx_network_default() {
995 assert_eq!(DydxNetwork::default(), DydxNetwork::Mainnet);
997 }
998
999 #[rstest]
1000 fn test_dydx_network_serde_lowercase() {
1001 let mainnet = DydxNetwork::Mainnet;
1003 let json = serde_json::to_string(&mainnet).unwrap();
1004 assert_eq!(json, "\"mainnet\"");
1005
1006 let deserialized: DydxNetwork = serde_json::from_str("\"mainnet\"").unwrap();
1007 assert_eq!(deserialized, DydxNetwork::Mainnet);
1008
1009 let testnet = DydxNetwork::Testnet;
1010 let json = serde_json::to_string(&testnet).unwrap();
1011 assert_eq!(json, "\"testnet\"");
1012
1013 let deserialized: DydxNetwork = serde_json::from_str("\"testnet\"").unwrap();
1014 assert_eq!(deserialized, DydxNetwork::Testnet);
1015 }
1016}