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(module = "nautilus_trader.adapters.dydx", eq, eq_int, from_py_object)
119)]
120#[cfg_attr(
121 feature = "python",
122 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.dydx")
123)]
124pub enum DydxOrderSide {
125 Buy,
127 Sell,
129}
130
131impl From<OrderSide> for DydxOrderSide {
132 fn from(value: OrderSide) -> Self {
133 match value {
134 OrderSide::Buy => Self::Buy,
135 OrderSide::Sell => Self::Sell,
136 }
137 }
138}
139
140impl From<DydxOrderSide> for OrderSide {
141 fn from(side: DydxOrderSide) -> Self {
142 match side {
143 DydxOrderSide::Buy => Self::Buy,
144 DydxOrderSide::Sell => Self::Sell,
145 }
146 }
147}
148
149#[derive(
151 Copy,
152 Clone,
153 Debug,
154 Display,
155 PartialEq,
156 Eq,
157 Hash,
158 AsRefStr,
159 EnumIter,
160 EnumString,
161 Serialize,
162 Deserialize,
163)]
164#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
165#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
166#[cfg_attr(
167 feature = "python",
168 pyo3::pyclass(module = "nautilus_trader.adapters.dydx", eq, eq_int, from_py_object)
169)]
170#[cfg_attr(
171 feature = "python",
172 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.dydx")
173)]
174pub enum DydxOrderType {
175 Limit,
177 Market,
179 StopLimit,
181 StopMarket,
183 #[serde(rename = "TAKE_PROFIT", alias = "TAKE_PROFIT_LIMIT")]
185 #[strum(serialize = "TAKE_PROFIT", serialize = "TAKE_PROFIT_LIMIT")]
186 TakeProfitLimit,
187 TakeProfitMarket,
189 TrailingStop,
191}
192
193impl TryFrom<OrderType> for DydxOrderType {
194 type Error = DydxError;
195
196 fn try_from(value: OrderType) -> Result<Self, Self::Error> {
197 match value {
198 OrderType::Market => Ok(Self::Market),
199 OrderType::Limit => Ok(Self::Limit),
200 OrderType::StopMarket => Ok(Self::StopMarket),
201 OrderType::StopLimit => Ok(Self::StopLimit),
202 OrderType::MarketIfTouched => Ok(Self::TakeProfitMarket),
203 OrderType::LimitIfTouched => Ok(Self::TakeProfitLimit),
204 OrderType::TrailingStopMarket | OrderType::TrailingStopLimit => Ok(Self::TrailingStop),
205 OrderType::MarketToLimit => Err(DydxError::UnsupportedOrderType(format!("{value:?}"))),
206 }
207 }
208}
209
210impl DydxOrderType {
211 pub fn try_from_order_type(value: OrderType) -> anyhow::Result<Self> {
217 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
218 }
219
220 #[must_use]
222 pub const fn is_conditional(&self) -> bool {
223 matches!(
224 self,
225 Self::StopLimit
226 | Self::StopMarket
227 | Self::TakeProfitLimit
228 | Self::TakeProfitMarket
229 | Self::TrailingStop
230 )
231 }
232
233 #[must_use]
235 pub const fn condition_type(&self) -> DydxConditionType {
236 match self {
237 Self::StopLimit | Self::StopMarket => DydxConditionType::StopLoss,
238 Self::TakeProfitLimit | Self::TakeProfitMarket => DydxConditionType::TakeProfit,
239 _ => DydxConditionType::Unspecified,
240 }
241 }
242
243 #[must_use]
245 pub const fn is_market_execution(&self) -> bool {
246 matches!(
247 self,
248 Self::Market | Self::StopMarket | Self::TakeProfitMarket
249 )
250 }
251}
252
253impl From<DydxOrderType> for OrderType {
254 fn from(value: DydxOrderType) -> Self {
255 match value {
256 DydxOrderType::Market => Self::Market,
257 DydxOrderType::Limit => Self::Limit,
258 DydxOrderType::StopMarket => Self::StopMarket,
259 DydxOrderType::StopLimit => Self::StopLimit,
260 DydxOrderType::TakeProfitMarket => Self::MarketIfTouched,
261 DydxOrderType::TakeProfitLimit => Self::LimitIfTouched,
262 DydxOrderType::TrailingStop => Self::TrailingStopMarket,
263 }
264 }
265}
266
267#[derive(
269 Copy,
270 Clone,
271 Debug,
272 Display,
273 PartialEq,
274 Eq,
275 Hash,
276 AsRefStr,
277 EnumIter,
278 EnumString,
279 Serialize,
280 Deserialize,
281)]
282#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
283pub enum DydxOrderExecution {
284 Default,
286 Ioc,
288 Fok,
290 PostOnly,
292}
293
294#[derive(
296 Copy, Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumIter, Serialize, Deserialize,
297)]
298pub enum DydxOrderFlags {
299 ShortTerm = 0,
301 Conditional = 32,
303 LongTerm = 64,
305}
306
307#[derive(
313 Copy,
314 Clone,
315 Debug,
316 Display,
317 PartialEq,
318 Eq,
319 Hash,
320 AsRefStr,
321 EnumIter,
322 EnumString,
323 Serialize,
324 Deserialize,
325)]
326#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
327pub enum DydxConditionType {
328 Unspecified,
330 StopLoss,
332 TakeProfit,
334}
335
336#[derive(
338 Copy,
339 Clone,
340 Debug,
341 Display,
342 PartialEq,
343 Eq,
344 Hash,
345 AsRefStr,
346 EnumIter,
347 EnumString,
348 Serialize,
349 Deserialize,
350)]
351#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
352pub enum DydxPositionSide {
353 Long,
355 Short,
357}
358
359impl From<DydxPositionSide> for PositionSide {
360 fn from(value: DydxPositionSide) -> Self {
361 match value {
362 DydxPositionSide::Long => Self::Long,
363 DydxPositionSide::Short => Self::Short,
364 }
365 }
366}
367
368#[derive(
370 Copy,
371 Clone,
372 Debug,
373 Display,
374 PartialEq,
375 Eq,
376 Hash,
377 AsRefStr,
378 EnumIter,
379 EnumString,
380 Serialize,
381 Deserialize,
382)]
383#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
384pub enum DydxPositionStatus {
385 Open,
387 Closed,
389 Liquidated,
391}
392
393impl DydxPositionStatus {
394 #[must_use]
396 pub const fn is_closed(&self) -> bool {
397 matches!(self, Self::Closed | Self::Liquidated)
398 }
399}
400
401#[derive(
403 Copy,
404 Clone,
405 Debug,
406 Display,
407 PartialEq,
408 Eq,
409 Hash,
410 AsRefStr,
411 EnumIter,
412 EnumString,
413 Serialize,
414 Deserialize,
415)]
416#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
417pub enum DydxMarketStatus {
418 Active,
420 Paused,
422 CancelOnly,
424 PostOnly,
426 Initializing,
428 FinalSettlement,
430 #[serde(other)]
432 Unknown,
433}
434
435impl From<DydxMarketStatus> for MarketStatusAction {
436 fn from(value: DydxMarketStatus) -> Self {
437 match value {
438 DydxMarketStatus::Active => Self::Trading,
439 DydxMarketStatus::Paused => Self::Pause,
440 DydxMarketStatus::CancelOnly => Self::Halt,
441 DydxMarketStatus::PostOnly => Self::Quoting,
442 DydxMarketStatus::Initializing => Self::PreOpen,
443 DydxMarketStatus::FinalSettlement => Self::Close,
444 DydxMarketStatus::Unknown => Self::None,
446 }
447 }
448}
449
450#[derive(
452 Copy,
453 Clone,
454 Debug,
455 Display,
456 PartialEq,
457 Eq,
458 Hash,
459 AsRefStr,
460 EnumIter,
461 EnumString,
462 Serialize,
463 Deserialize,
464)]
465#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
466pub enum DydxFillType {
467 Limit,
469 Liquidated,
471 Liquidation,
473 Deleveraged,
475 Offsetting,
477 #[serde(other)]
479 Unknown,
480}
481
482#[derive(
484 Copy,
485 Clone,
486 Debug,
487 Display,
488 PartialEq,
489 Eq,
490 Hash,
491 AsRefStr,
492 EnumIter,
493 EnumString,
494 Serialize,
495 Deserialize,
496)]
497#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
498pub enum DydxLiquidity {
499 Maker,
501 Taker,
503}
504
505impl From<DydxLiquidity> for LiquiditySide {
506 fn from(value: DydxLiquidity) -> Self {
507 match value {
508 DydxLiquidity::Maker => Self::Maker,
509 DydxLiquidity::Taker => Self::Taker,
510 }
511 }
512}
513
514impl From<LiquiditySide> for DydxLiquidity {
515 fn from(value: LiquiditySide) -> Self {
516 match value {
517 LiquiditySide::Maker => Self::Maker,
518 LiquiditySide::Taker => Self::Taker,
519 LiquiditySide::NoLiquiditySide => Self::Taker, }
521 }
522}
523
524#[derive(
526 Copy,
527 Clone,
528 Debug,
529 Display,
530 PartialEq,
531 Eq,
532 Hash,
533 AsRefStr,
534 EnumIter,
535 EnumString,
536 Serialize,
537 Deserialize,
538)]
539#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
540pub enum DydxTickerType {
541 Perpetual,
543 #[serde(other)]
545 Unknown,
546}
547
548#[derive(
552 Copy,
553 Clone,
554 Debug,
555 Display,
556 PartialEq,
557 Eq,
558 Hash,
559 AsRefStr,
560 EnumIter,
561 EnumString,
562 Serialize,
563 Deserialize,
564)]
565#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
566pub enum DydxTradeType {
567 Limit,
569 Market,
571 Liquidated,
573 TwapSuborder,
575 StopLimit,
577 TakeProfitLimit,
579 #[serde(other)]
581 Unknown,
582}
583
584#[derive(
586 Copy,
587 Clone,
588 Debug,
589 Display,
590 PartialEq,
591 Eq,
592 Hash,
593 AsRefStr,
594 EnumIter,
595 EnumString,
596 Serialize,
597 Deserialize,
598)]
599#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
600#[cfg_attr(
601 feature = "python",
602 pyo3::pyclass(module = "nautilus_trader.adapters.dydx", eq, eq_int, from_py_object)
603)]
604pub enum DydxTransferType {
605 TransferIn,
607 TransferOut,
609 Deposit,
611 Withdrawal,
613}
614
615#[derive(
617 Copy,
618 Clone,
619 Debug,
620 Display,
621 PartialEq,
622 Eq,
623 Hash,
624 AsRefStr,
625 IntoStaticStr,
626 EnumIter,
627 EnumString,
628 Serialize,
629 Deserialize,
630)]
631#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
632#[derive(Default)]
633#[cfg_attr(
634 feature = "python",
635 pyo3::pyclass(module = "nautilus_trader.adapters.dydx", eq, eq_int, from_py_object)
636)]
637pub enum DydxCandleResolution {
638 #[serde(rename = "1MIN")]
640 #[strum(serialize = "1MIN")]
641 #[default]
642 OneMinute,
643 #[serde(rename = "5MINS")]
645 #[strum(serialize = "5MINS")]
646 FiveMinutes,
647 #[serde(rename = "15MINS")]
649 #[strum(serialize = "15MINS")]
650 FifteenMinutes,
651 #[serde(rename = "30MINS")]
653 #[strum(serialize = "30MINS")]
654 ThirtyMinutes,
655 #[serde(rename = "1HOUR")]
657 #[strum(serialize = "1HOUR")]
658 OneHour,
659 #[serde(rename = "4HOURS")]
661 #[strum(serialize = "4HOURS")]
662 FourHours,
663 #[serde(rename = "1DAY")]
665 #[strum(serialize = "1DAY")]
666 OneDay,
667}
668
669impl DydxCandleResolution {
670 pub fn from_bar_spec(spec: &BarSpecification) -> anyhow::Result<Self> {
676 match spec.step.get() {
677 1 => match spec.aggregation {
678 BarAggregation::Minute => Ok(Self::OneMinute),
679 BarAggregation::Hour => Ok(Self::OneHour),
680 BarAggregation::Day => Ok(Self::OneDay),
681 _ => anyhow::bail!("Unsupported bar aggregation: {:?}", spec.aggregation),
682 },
683 5 if spec.aggregation == BarAggregation::Minute => Ok(Self::FiveMinutes),
684 15 if spec.aggregation == BarAggregation::Minute => Ok(Self::FifteenMinutes),
685 30 if spec.aggregation == BarAggregation::Minute => Ok(Self::ThirtyMinutes),
686 4 if spec.aggregation == BarAggregation::Hour => Ok(Self::FourHours),
687 step => anyhow::bail!(
688 "Unsupported bar step: {step} with aggregation {:?}",
689 spec.aggregation
690 ),
691 }
692 }
693}
694
695#[derive(
699 Copy,
700 Clone,
701 Debug,
702 Default,
703 Display,
704 PartialEq,
705 Eq,
706 Hash,
707 AsRefStr,
708 EnumIter,
709 EnumString,
710 Serialize,
711 Deserialize,
712)]
713#[strum(serialize_all = "lowercase")]
714#[serde(rename_all = "lowercase")]
715#[cfg_attr(
716 feature = "python",
717 pyo3::pyclass(
718 eq,
719 eq_int,
720 module = "nautilus_trader.adapters.dydx",
721 from_py_object,
722 rename_all = "SCREAMING_SNAKE_CASE",
723 )
724)]
725#[cfg_attr(
726 feature = "python",
727 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.dydx")
728)]
729pub enum DydxNetwork {
730 #[default]
732 Mainnet,
733 Testnet,
735}
736
737impl DydxNetwork {
738 #[must_use]
740 pub const fn chain_id(self) -> ChainId {
741 match self {
742 Self::Mainnet => ChainId::Mainnet1,
743 Self::Testnet => ChainId::Testnet4,
744 }
745 }
746
747 #[must_use]
749 pub const fn as_str(self) -> &'static str {
750 match self {
751 Self::Mainnet => "mainnet",
752 Self::Testnet => "testnet",
753 }
754 }
755}
756
757#[cfg(test)]
758mod tests {
759 use rstest::rstest;
760
761 use super::*;
762
763 #[rstest]
764 fn test_reference_enums_tolerate_unmodeled_values() {
765 let status: DydxMarketStatus = serde_json::from_str("\"SOME_NEW_STATUS\"").unwrap();
768 let ticker: DydxTickerType = serde_json::from_str("\"SPOT\"").unwrap();
769 let trade: DydxTradeType = serde_json::from_str("\"SOME_NEW_TRADE\"").unwrap();
770 let fill: DydxFillType = serde_json::from_str("\"SOME_NEW_FILL\"").unwrap();
771 assert_eq!(status, DydxMarketStatus::Unknown);
772 assert_eq!(ticker, DydxTickerType::Unknown);
773 assert_eq!(trade, DydxTradeType::Unknown);
774 assert_eq!(fill, DydxFillType::Unknown);
775 assert_eq!(
776 MarketStatusAction::from(DydxMarketStatus::Unknown),
777 MarketStatusAction::None
778 );
779 }
780
781 #[rstest]
782 fn test_order_status_conversion() {
783 assert_eq!(
784 OrderStatus::from(DydxOrderStatus::Open),
785 OrderStatus::Accepted
786 );
787 assert_eq!(
788 OrderStatus::from(DydxOrderStatus::Filled),
789 OrderStatus::Filled
790 );
791 assert_eq!(
792 OrderStatus::from(DydxOrderStatus::Canceled),
793 OrderStatus::Canceled
794 );
795 }
796
797 #[rstest]
798 fn test_liquidity_conversion() {
799 assert_eq!(
800 LiquiditySide::from(DydxLiquidity::Maker),
801 LiquiditySide::Maker
802 );
803 assert_eq!(
804 LiquiditySide::from(DydxLiquidity::Taker),
805 LiquiditySide::Taker
806 );
807 }
808
809 #[rstest]
810 fn test_order_type_is_conditional() {
811 assert!(DydxOrderType::StopLimit.is_conditional());
812 assert!(DydxOrderType::StopMarket.is_conditional());
813 assert!(DydxOrderType::TakeProfitLimit.is_conditional());
814 assert!(DydxOrderType::TakeProfitMarket.is_conditional());
815 assert!(DydxOrderType::TrailingStop.is_conditional());
816 assert!(!DydxOrderType::Limit.is_conditional());
817 assert!(!DydxOrderType::Market.is_conditional());
818 }
819
820 #[rstest]
821 fn test_condition_type_mapping() {
822 assert_eq!(
823 DydxOrderType::StopLimit.condition_type(),
824 DydxConditionType::StopLoss
825 );
826 assert_eq!(
827 DydxOrderType::StopMarket.condition_type(),
828 DydxConditionType::StopLoss
829 );
830 assert_eq!(
831 DydxOrderType::TakeProfitLimit.condition_type(),
832 DydxConditionType::TakeProfit
833 );
834 assert_eq!(
835 DydxOrderType::TakeProfitMarket.condition_type(),
836 DydxConditionType::TakeProfit
837 );
838 assert_eq!(
839 DydxOrderType::Limit.condition_type(),
840 DydxConditionType::Unspecified
841 );
842 }
843
844 #[rstest]
845 fn test_is_market_execution() {
846 assert!(DydxOrderType::Market.is_market_execution());
847 assert!(DydxOrderType::StopMarket.is_market_execution());
848 assert!(DydxOrderType::TakeProfitMarket.is_market_execution());
849 assert!(!DydxOrderType::Limit.is_market_execution());
850 assert!(!DydxOrderType::StopLimit.is_market_execution());
851 assert!(!DydxOrderType::TakeProfitLimit.is_market_execution());
852 }
853
854 #[rstest]
855 fn test_order_type_to_nautilus() {
856 assert_eq!(OrderType::from(DydxOrderType::Market), OrderType::Market);
857 assert_eq!(OrderType::from(DydxOrderType::Limit), OrderType::Limit);
858 assert_eq!(
859 OrderType::from(DydxOrderType::StopMarket),
860 OrderType::StopMarket
861 );
862 assert_eq!(
863 OrderType::from(DydxOrderType::StopLimit),
864 OrderType::StopLimit
865 );
866 }
867
868 #[rstest]
869 fn test_order_side_conversion_from_nautilus() {
870 assert_eq!(DydxOrderSide::from(OrderSide::Buy), DydxOrderSide::Buy);
871 assert_eq!(DydxOrderSide::from(OrderSide::Sell), DydxOrderSide::Sell);
872 }
873
874 #[rstest]
875 fn test_order_side_conversion_to_nautilus() {
876 assert_eq!(OrderSide::from(DydxOrderSide::Buy), OrderSide::Buy);
877 assert_eq!(OrderSide::from(DydxOrderSide::Sell), OrderSide::Sell);
878 }
879
880 #[rstest]
881 fn test_order_type_conversion_from_nautilus() {
882 assert_eq!(
883 DydxOrderType::try_from(OrderType::Market).unwrap(),
884 DydxOrderType::Market
885 );
886 assert_eq!(
887 DydxOrderType::try_from(OrderType::Limit).unwrap(),
888 DydxOrderType::Limit
889 );
890 assert_eq!(
891 DydxOrderType::try_from(OrderType::StopMarket).unwrap(),
892 DydxOrderType::StopMarket
893 );
894 assert_eq!(
895 DydxOrderType::try_from(OrderType::StopLimit).unwrap(),
896 DydxOrderType::StopLimit
897 );
898 assert!(DydxOrderType::try_from(OrderType::MarketToLimit).is_err());
899 }
900
901 #[rstest]
902 fn test_order_type_conversion_to_nautilus() {
903 assert_eq!(OrderType::from(DydxOrderType::Market), OrderType::Market);
904 assert_eq!(OrderType::from(DydxOrderType::Limit), OrderType::Limit);
905 assert_eq!(
906 OrderType::from(DydxOrderType::StopMarket),
907 OrderType::StopMarket
908 );
909 assert_eq!(
910 OrderType::from(DydxOrderType::StopLimit),
911 OrderType::StopLimit
912 );
913 }
914
915 #[rstest]
919 #[case("\"TAKE_PROFIT\"", DydxOrderType::TakeProfitLimit)]
920 #[case("\"TAKE_PROFIT_LIMIT\"", DydxOrderType::TakeProfitLimit)]
921 #[case("\"TAKE_PROFIT_MARKET\"", DydxOrderType::TakeProfitMarket)]
922 fn test_dydx_order_type_take_profit_serde(
923 #[case] input: &str,
924 #[case] expected: DydxOrderType,
925 ) {
926 let parsed: DydxOrderType = serde_json::from_str(input).unwrap();
927 assert_eq!(parsed, expected);
928 }
929
930 #[rstest]
931 fn test_dydx_network_chain_id_mapping() {
932 assert_eq!(DydxNetwork::Mainnet.chain_id(), ChainId::Mainnet1);
934 assert_eq!(DydxNetwork::Testnet.chain_id(), ChainId::Testnet4);
935 }
936
937 #[rstest]
938 fn test_dydx_network_as_str() {
939 assert_eq!(DydxNetwork::Mainnet.as_str(), "mainnet");
941 assert_eq!(DydxNetwork::Testnet.as_str(), "testnet");
942 }
943
944 #[rstest]
945 fn test_dydx_network_default() {
946 assert_eq!(DydxNetwork::default(), DydxNetwork::Mainnet);
948 }
949
950 #[rstest]
951 fn test_dydx_network_serde_lowercase() {
952 let mainnet = DydxNetwork::Mainnet;
954 let json = serde_json::to_string(&mainnet).unwrap();
955 assert_eq!(json, "\"mainnet\"");
956
957 let deserialized: DydxNetwork = serde_json::from_str("\"mainnet\"").unwrap();
958 assert_eq!(deserialized, DydxNetwork::Mainnet);
959
960 let testnet = DydxNetwork::Testnet;
961 let json = serde_json::to_string(&testnet).unwrap();
962 assert_eq!(json, "\"testnet\"");
963
964 let deserialized: DydxNetwork = serde_json::from_str("\"testnet\"").unwrap();
965 assert_eq!(deserialized, DydxNetwork::Testnet);
966 }
967}