1use nautilus_model::{
19 data::BarSpecification,
20 enums::{
21 AggressorSide, AssetClass, BarAggregation, MarketStatusAction, OrderSide, OrderStatus,
22 PositionSide, TimeInForce,
23 },
24};
25use serde::{Deserialize, Serialize};
26use strum::{AsRefStr, Display, EnumIter, EnumString};
27
28use super::consts::{
29 AX_HTTP_SANDBOX_URL, AX_HTTP_URL, AX_ORDERS_SANDBOX_URL, AX_ORDERS_URL, AX_WS_PRIVATE_URL,
30 AX_WS_PUBLIC_URL, AX_WS_SANDBOX_PRIVATE_URL, AX_WS_SANDBOX_PUBLIC_URL,
31};
32
33#[derive(
35 Clone,
36 Copy,
37 Debug,
38 Default,
39 Display,
40 Eq,
41 PartialEq,
42 Hash,
43 AsRefStr,
44 EnumIter,
45 EnumString,
46 Serialize,
47 Deserialize,
48)]
49#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
50#[strum(ascii_case_insensitive)]
51#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
52#[cfg_attr(
53 feature = "python",
54 pyo3::pyclass(
55 eq,
56 eq_int,
57 frozen,
58 hash,
59 module = "nautilus_trader.adapters.architect_ax",
60 from_py_object,
61 rename_all = "SCREAMING_SNAKE_CASE",
62 )
63)]
64#[cfg_attr(
65 feature = "python",
66 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.architect_ax")
67)]
68pub enum AxEnvironment {
69 #[default]
71 Sandbox,
72 Production,
74}
75
76impl AxEnvironment {
77 #[must_use]
79 pub const fn http_url(&self) -> &'static str {
80 match self {
81 Self::Sandbox => AX_HTTP_SANDBOX_URL,
82 Self::Production => AX_HTTP_URL,
83 }
84 }
85
86 #[must_use]
88 pub const fn orders_url(&self) -> &'static str {
89 match self {
90 Self::Sandbox => AX_ORDERS_SANDBOX_URL,
91 Self::Production => AX_ORDERS_URL,
92 }
93 }
94
95 #[must_use]
97 pub const fn ws_md_url(&self) -> &'static str {
98 match self {
99 Self::Sandbox => AX_WS_SANDBOX_PUBLIC_URL,
100 Self::Production => AX_WS_PUBLIC_URL,
101 }
102 }
103
104 #[must_use]
106 pub const fn ws_orders_url(&self) -> &'static str {
107 match self {
108 Self::Sandbox => AX_WS_SANDBOX_PRIVATE_URL,
109 Self::Production => AX_WS_PRIVATE_URL,
110 }
111 }
112}
113
114#[derive(
119 Clone,
120 Copy,
121 Debug,
122 Display,
123 Eq,
124 PartialEq,
125 Hash,
126 AsRefStr,
127 EnumIter,
128 EnumString,
129 Serialize,
130 Deserialize,
131)]
132#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
133#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
134#[cfg_attr(
135 feature = "python",
136 pyo3::pyclass(
137 eq,
138 eq_int,
139 frozen,
140 hash,
141 module = "nautilus_trader.adapters.architect_ax",
142 from_py_object,
143 rename_all = "SCREAMING_SNAKE_CASE",
144 )
145)]
146pub enum AxInstrumentState {
147 PreOpen,
149 Open,
151 Closed,
153 ClosedFrozen,
155 Halted,
157 MatchAndCloseAuction,
159 Suspended,
161 Delisted,
163 #[serde(other)]
165 Unknown,
166}
167
168impl AxInstrumentState {
169 #[must_use]
171 pub fn is_tradeable(self) -> bool {
172 matches!(self, Self::Open | Self::PreOpen)
173 }
174}
175
176impl From<AxInstrumentState> for MarketStatusAction {
177 fn from(state: AxInstrumentState) -> Self {
178 match state {
179 AxInstrumentState::PreOpen => Self::PreOpen,
180 AxInstrumentState::Open => Self::Trading,
181 AxInstrumentState::Closed | AxInstrumentState::ClosedFrozen => Self::Close,
182 AxInstrumentState::Halted => Self::Halt,
183 AxInstrumentState::MatchAndCloseAuction => Self::Cross,
184 AxInstrumentState::Suspended => Self::Suspend,
185 AxInstrumentState::Delisted | AxInstrumentState::Unknown => {
186 Self::NotAvailableForTrading
187 }
188 }
189 }
190}
191
192#[derive(
199 Clone,
200 Copy,
201 Debug,
202 Display,
203 Eq,
204 PartialEq,
205 Hash,
206 AsRefStr,
207 EnumIter,
208 EnumString,
209 Serialize,
210 Deserialize,
211)]
212#[serde(rename_all = "snake_case")]
213#[strum(serialize_all = "lowercase")]
214pub enum AxCategory {
215 Fx,
216 Equities,
217 Metals,
218 Energy,
219 EnergyEtfs,
220 Treasuries,
221 Compute,
222 Crypto,
223 #[serde(other)]
224 Unknown,
225}
226
227impl From<AxCategory> for AssetClass {
228 fn from(category: AxCategory) -> Self {
229 match category {
230 AxCategory::Fx => Self::FX,
231 AxCategory::Equities | AxCategory::EnergyEtfs => Self::Equity,
232 AxCategory::Metals | AxCategory::Energy => Self::Commodity,
233 AxCategory::Crypto => Self::Cryptocurrency,
234 AxCategory::Treasuries => Self::Debt,
235 AxCategory::Compute | AxCategory::Unknown => Self::Alternative,
236 }
237 }
238}
239
240#[derive(
245 Clone,
246 Copy,
247 Debug,
248 Display,
249 Eq,
250 PartialEq,
251 Hash,
252 AsRefStr,
253 EnumIter,
254 EnumString,
255 Serialize,
256 Deserialize,
257)]
258#[cfg_attr(
259 feature = "python",
260 pyo3::pyclass(
261 eq,
262 eq_int,
263 frozen,
264 hash,
265 module = "nautilus_trader.adapters.architect_ax",
266 from_py_object,
267 rename_all = "SCREAMING_SNAKE_CASE",
268 )
269)]
270pub enum AxOrderSide {
271 #[serde(rename = "B")]
273 #[strum(serialize = "B")]
274 Buy,
275 #[serde(rename = "S")]
277 #[strum(serialize = "S")]
278 Sell,
279}
280
281impl From<AxOrderSide> for AggressorSide {
282 fn from(side: AxOrderSide) -> Self {
283 match side {
284 AxOrderSide::Buy => Self::Buy,
285 AxOrderSide::Sell => Self::Sell,
286 }
287 }
288}
289
290impl From<AxOrderSide> for OrderSide {
291 fn from(side: AxOrderSide) -> Self {
292 match side {
293 AxOrderSide::Buy => Self::Buy,
294 AxOrderSide::Sell => Self::Sell,
295 }
296 }
297}
298
299impl From<AxOrderSide> for PositionSide {
300 fn from(side: AxOrderSide) -> Self {
301 match side {
302 AxOrderSide::Buy => Self::Long,
303 AxOrderSide::Sell => Self::Short,
304 }
305 }
306}
307
308impl From<OrderSide> for AxOrderSide {
309 fn from(side: OrderSide) -> Self {
310 match side {
311 OrderSide::Buy => Self::Buy,
312 OrderSide::Sell => Self::Sell,
313 }
314 }
315}
316
317#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
322#[serde(rename_all = "snake_case")]
323pub enum AxFundingVariant {
324 DailyClose,
326 IntradayTwap,
328}
329
330#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
335#[serde(rename_all = "snake_case")]
336pub enum AxFundingSlotStatus {
337 Realized,
339 Projected,
341 Skipped,
343 Pending,
345}
346
347#[derive(
352 Clone,
353 Copy,
354 Debug,
355 Display,
356 Eq,
357 PartialEq,
358 Hash,
359 AsRefStr,
360 EnumIter,
361 EnumString,
362 Serialize,
363 Deserialize,
364)]
365#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
366#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
367#[cfg_attr(
368 feature = "python",
369 pyo3::pyclass(
370 eq,
371 eq_int,
372 frozen,
373 hash,
374 module = "nautilus_trader.adapters.architect_ax",
375 from_py_object,
376 rename_all = "SCREAMING_SNAKE_CASE",
377 )
378)]
379pub enum AxOrderStatus {
380 Pending,
382 Accepted,
384 PartiallyFilled,
386 Filled,
388 Canceling,
390 Canceled,
392 Rejected,
394 Expired,
396 Replaced,
398 DoneForDay,
400 Out,
402 ReconciledOut,
404 Stale,
406 Unknown,
408}
409
410impl From<AxOrderStatus> for OrderStatus {
411 fn from(status: AxOrderStatus) -> Self {
412 match status {
413 AxOrderStatus::Pending => Self::Submitted,
414 AxOrderStatus::Accepted => Self::Accepted,
415 AxOrderStatus::PartiallyFilled => Self::PartiallyFilled,
416 AxOrderStatus::Filled => Self::Filled,
417 AxOrderStatus::Canceling => Self::PendingCancel,
418 AxOrderStatus::Canceled => Self::Canceled,
419 AxOrderStatus::Rejected => Self::Rejected,
420 AxOrderStatus::Expired => Self::Expired,
421 AxOrderStatus::Replaced => Self::Accepted,
422 AxOrderStatus::DoneForDay => Self::Canceled,
423 AxOrderStatus::Out => Self::Canceled,
424 AxOrderStatus::ReconciledOut => Self::Canceled,
425 AxOrderStatus::Stale => Self::Accepted,
426 AxOrderStatus::Unknown => Self::Initialized,
427 }
428 }
429}
430
431#[derive(
436 Clone,
437 Copy,
438 Debug,
439 Display,
440 Eq,
441 PartialEq,
442 Hash,
443 AsRefStr,
444 EnumIter,
445 EnumString,
446 Serialize,
447 Deserialize,
448)]
449#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
450#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
451#[cfg_attr(
452 feature = "python",
453 pyo3::pyclass(
454 eq,
455 eq_int,
456 frozen,
457 hash,
458 module = "nautilus_trader.adapters.architect_ax",
459 from_py_object,
460 rename_all = "SCREAMING_SNAKE_CASE",
461 )
462)]
463pub enum AxTimeInForce {
464 Gtc,
466 Gtd,
468 Day,
470 Ioc,
472 Fok,
474 Ato,
476 Atc,
478}
479
480impl From<AxTimeInForce> for TimeInForce {
481 fn from(tif: AxTimeInForce) -> Self {
482 match tif {
483 AxTimeInForce::Gtc => Self::Gtc,
484 AxTimeInForce::Gtd => Self::Gtd,
485 AxTimeInForce::Day => Self::Day,
486 AxTimeInForce::Ioc => Self::Ioc,
487 AxTimeInForce::Fok => Self::Fok,
488 AxTimeInForce::Ato => Self::AtTheOpen,
489 AxTimeInForce::Atc => Self::AtTheClose,
490 }
491 }
492}
493
494impl TryFrom<TimeInForce> for AxTimeInForce {
495 type Error = &'static str;
496
497 fn try_from(tif: TimeInForce) -> Result<Self, Self::Error> {
498 match tif {
499 TimeInForce::Gtc => Ok(Self::Gtc),
500 TimeInForce::Gtd => Ok(Self::Gtd),
501 TimeInForce::Day => Ok(Self::Day),
502 TimeInForce::Ioc => Ok(Self::Ioc),
503 TimeInForce::Fok => Ok(Self::Fok),
504 TimeInForce::AtTheOpen => Ok(Self::Ato),
505 TimeInForce::AtTheClose => Ok(Self::Atc),
506 }
507 }
508}
509
510#[derive(
521 Clone,
522 Copy,
523 Debug,
524 Display,
525 Eq,
526 PartialEq,
527 Hash,
528 AsRefStr,
529 EnumIter,
530 EnumString,
531 Serialize,
532 Deserialize,
533)]
534#[strum(ascii_case_insensitive)]
535#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
536#[cfg_attr(
537 feature = "python",
538 pyo3::pyclass(
539 eq,
540 eq_int,
541 frozen,
542 hash,
543 module = "nautilus_trader.adapters.architect_ax",
544 from_py_object,
545 rename_all = "SCREAMING_SNAKE_CASE",
546 )
547)]
548#[cfg_attr(
549 feature = "python",
550 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.architect_ax")
551)]
552pub enum AxMarketDataLevel {
553 #[serde(rename = "LEVEL_1")]
555 #[strum(serialize = "LEVEL_1")]
556 Level1,
557 #[serde(rename = "LEVEL_2")]
559 #[strum(serialize = "LEVEL_2")]
560 Level2,
561 #[serde(rename = "LEVEL_3")]
563 #[strum(serialize = "LEVEL_3")]
564 Level3,
565 #[serde(rename = "TRADES")]
567 #[strum(serialize = "TRADES")]
568 Trades,
569}
570
571#[derive(
576 Clone,
577 Copy,
578 Debug,
579 Display,
580 Eq,
581 PartialEq,
582 Hash,
583 AsRefStr,
584 EnumIter,
585 EnumString,
586 Serialize,
587 Deserialize,
588)]
589pub enum AxCandleWidth {
590 #[serde(rename = "1s")]
592 #[strum(serialize = "1s")]
593 Seconds1,
594 #[serde(rename = "5s")]
596 #[strum(serialize = "5s")]
597 Seconds5,
598 #[serde(rename = "1m")]
600 #[strum(serialize = "1m")]
601 Minutes1,
602 #[serde(rename = "5m")]
604 #[strum(serialize = "5m")]
605 Minutes5,
606 #[serde(rename = "15m")]
608 #[strum(serialize = "15m")]
609 Minutes15,
610 #[serde(rename = "1h")]
612 #[strum(serialize = "1h")]
613 Hours1,
614 #[serde(rename = "1d")]
616 #[strum(serialize = "1d")]
617 Days1,
618}
619
620impl TryFrom<&BarSpecification> for AxCandleWidth {
621 type Error = anyhow::Error;
622
623 fn try_from(spec: &BarSpecification) -> Result<Self, Self::Error> {
624 let step = spec.step.get();
625 match (step, spec.aggregation) {
626 (1, BarAggregation::Second) => Ok(Self::Seconds1),
627 (5, BarAggregation::Second) => Ok(Self::Seconds5),
628 (1, BarAggregation::Minute) => Ok(Self::Minutes1),
629 (5, BarAggregation::Minute) => Ok(Self::Minutes5),
630 (15, BarAggregation::Minute) => Ok(Self::Minutes15),
631 (1, BarAggregation::Hour) => Ok(Self::Hours1),
632 (1, BarAggregation::Day) => Ok(Self::Days1),
633 _ => anyhow::bail!(
634 "Unsupported bar specification for AX: {step}-{:?}",
635 spec.aggregation,
636 ),
637 }
638 }
639}
640
641#[derive(
646 Clone,
647 Copy,
648 Debug,
649 Display,
650 Eq,
651 PartialEq,
652 Hash,
653 AsRefStr,
654 EnumIter,
655 EnumString,
656 Serialize,
657 Deserialize,
658)]
659#[serde(rename_all = "snake_case")]
660#[strum(serialize_all = "snake_case")]
661pub enum AxMdRequestType {
662 Subscribe,
664 Unsubscribe,
666 SubscribeCandles,
668 UnsubscribeCandles,
670}
671
672#[derive(
677 Clone,
678 Copy,
679 Debug,
680 Display,
681 Eq,
682 PartialEq,
683 Hash,
684 AsRefStr,
685 EnumIter,
686 EnumString,
687 Serialize,
688 Deserialize,
689)]
690pub enum AxOrderRequestType {
691 #[serde(rename = "p")]
693 #[strum(serialize = "p")]
694 PlaceOrder,
695 #[serde(rename = "x")]
697 #[strum(serialize = "x")]
698 CancelOrder,
699 #[serde(rename = "o")]
701 #[strum(serialize = "o")]
702 GetOpenOrders,
703}
704
705#[derive(
710 Clone,
711 Copy,
712 Debug,
713 Display,
714 Eq,
715 PartialEq,
716 Hash,
717 AsRefStr,
718 EnumIter,
719 EnumString,
720 Serialize,
721 Deserialize,
722)]
723#[cfg_attr(
724 feature = "python",
725 pyo3::pyclass(
726 eq,
727 eq_int,
728 frozen,
729 hash,
730 module = "nautilus_trader.adapters.architect_ax",
731 from_py_object,
732 rename_all = "SCREAMING_SNAKE_CASE",
733 )
734)]
735pub enum AxMdWsMessageType {
736 #[serde(rename = "h")]
738 #[strum(serialize = "h")]
739 Heartbeat,
740 #[serde(rename = "s")]
742 #[strum(serialize = "s")]
743 Ticker,
744 #[serde(rename = "t")]
746 #[strum(serialize = "t")]
747 Trade,
748 #[serde(rename = "c")]
750 #[strum(serialize = "c")]
751 Candle,
752 #[serde(rename = "1")]
754 #[strum(serialize = "1")]
755 BookLevel1,
756 #[serde(rename = "2")]
758 #[strum(serialize = "2")]
759 BookLevel2,
760 #[serde(rename = "3")]
762 #[strum(serialize = "3")]
763 BookLevel3,
764}
765
766#[derive(
771 Clone,
772 Copy,
773 Debug,
774 Display,
775 Eq,
776 PartialEq,
777 Hash,
778 AsRefStr,
779 EnumIter,
780 EnumString,
781 Serialize,
782 Deserialize,
783)]
784#[cfg_attr(
785 feature = "python",
786 pyo3::pyclass(
787 eq,
788 eq_int,
789 frozen,
790 hash,
791 module = "nautilus_trader.adapters.architect_ax",
792 from_py_object,
793 rename_all = "SCREAMING_SNAKE_CASE",
794 )
795)]
796pub enum AxOrderWsMessageType {
797 #[serde(rename = "h")]
799 #[strum(serialize = "h")]
800 Heartbeat,
801 #[serde(rename = "e")]
803 #[strum(serialize = "e")]
804 CancelRejected,
805 #[serde(rename = "n")]
807 #[strum(serialize = "n")]
808 OrderAcknowledged,
809 #[serde(rename = "c")]
811 #[strum(serialize = "c")]
812 OrderCanceled,
813 #[serde(rename = "r")]
815 #[strum(serialize = "r")]
816 OrderReplaced,
817 #[serde(rename = "j")]
819 #[strum(serialize = "j")]
820 OrderRejected,
821 #[serde(rename = "x")]
823 #[strum(serialize = "x")]
824 OrderExpired,
825 #[serde(rename = "d")]
827 #[strum(serialize = "d")]
828 OrderDoneForDay,
829 #[serde(rename = "p")]
831 #[strum(serialize = "p")]
832 OrderPartiallyFilled,
833 #[serde(rename = "f")]
835 #[strum(serialize = "f")]
836 OrderFilled,
837}
838
839#[derive(
844 Clone,
845 Copy,
846 Debug,
847 Display,
848 Eq,
849 PartialEq,
850 Hash,
851 AsRefStr,
852 EnumIter,
853 EnumString,
854 Serialize,
855 Deserialize,
856)]
857#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
858#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
859#[cfg_attr(
860 feature = "python",
861 pyo3::pyclass(
862 eq,
863 eq_int,
864 frozen,
865 hash,
866 module = "nautilus_trader.adapters.architect_ax",
867 from_py_object,
868 rename_all = "SCREAMING_SNAKE_CASE",
869 )
870)]
871pub enum AxCancelReason {
872 UserRequested,
874 #[serde(other)]
876 Unknown,
877}
878
879#[derive(
884 Clone,
885 Copy,
886 Debug,
887 Display,
888 Eq,
889 PartialEq,
890 Hash,
891 AsRefStr,
892 EnumIter,
893 EnumString,
894 Serialize,
895 Deserialize,
896)]
897#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
898#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
899#[cfg_attr(
900 feature = "python",
901 pyo3::pyclass(
902 eq,
903 eq_int,
904 frozen,
905 hash,
906 module = "nautilus_trader.adapters.architect_ax",
907 from_py_object,
908 rename_all = "SCREAMING_SNAKE_CASE",
909 )
910)]
911pub enum AxCancelRejectionReason {
912 OrderNotFound,
914 #[serde(other)]
916 Unknown,
917}
918
919#[cfg(test)]
920mod tests {
921 use rstest::rstest;
922
923 use super::*;
924
925 #[rstest]
926 #[case(AxInstrumentState::Open, "\"OPEN\"")]
927 #[case(AxInstrumentState::PreOpen, "\"PRE_OPEN\"")]
928 #[case(AxInstrumentState::Closed, "\"CLOSED\"")]
929 #[case(AxInstrumentState::ClosedFrozen, "\"CLOSED_FROZEN\"")]
930 #[case(AxInstrumentState::Halted, "\"HALTED\"")]
931 #[case(AxInstrumentState::MatchAndCloseAuction, "\"MATCH_AND_CLOSE_AUCTION\"")]
932 #[case(AxInstrumentState::Suspended, "\"SUSPENDED\"")]
933 #[case(AxInstrumentState::Delisted, "\"DELISTED\"")]
934 fn test_instrument_state_serialization(
935 #[case] state: AxInstrumentState,
936 #[case] expected: &str,
937 ) {
938 let json = serde_json::to_string(&state).unwrap();
939 assert_eq!(json, expected);
940
941 let parsed: AxInstrumentState = serde_json::from_str(&json).unwrap();
942 assert_eq!(parsed, state);
943 }
944
945 #[rstest]
946 fn test_instrument_state_unknown_string_deserializes_as_unknown() {
947 let parsed: AxInstrumentState = serde_json::from_str("\"SOME_FUTURE_STATE\"").unwrap();
948 assert_eq!(parsed, AxInstrumentState::Unknown);
949 }
950
951 #[rstest]
952 #[case(AxInstrumentState::PreOpen, true)]
953 #[case(AxInstrumentState::Open, true)]
954 #[case(AxInstrumentState::Closed, false)]
955 #[case(AxInstrumentState::ClosedFrozen, false)]
956 #[case(AxInstrumentState::Halted, false)]
957 #[case(AxInstrumentState::MatchAndCloseAuction, false)]
958 #[case(AxInstrumentState::Suspended, false)]
959 #[case(AxInstrumentState::Delisted, false)]
960 #[case(AxInstrumentState::Unknown, false)]
961 fn test_instrument_state_is_tradeable(
962 #[case] state: AxInstrumentState,
963 #[case] expected: bool,
964 ) {
965 assert_eq!(state.is_tradeable(), expected);
966 }
967
968 #[rstest]
969 #[case(AxInstrumentState::PreOpen, MarketStatusAction::PreOpen)]
970 #[case(AxInstrumentState::Open, MarketStatusAction::Trading)]
971 #[case(AxInstrumentState::Closed, MarketStatusAction::Close)]
972 #[case(AxInstrumentState::ClosedFrozen, MarketStatusAction::Close)]
973 #[case(AxInstrumentState::Halted, MarketStatusAction::Halt)]
974 #[case(AxInstrumentState::MatchAndCloseAuction, MarketStatusAction::Cross)]
975 #[case(AxInstrumentState::Suspended, MarketStatusAction::Suspend)]
976 #[case(
977 AxInstrumentState::Delisted,
978 MarketStatusAction::NotAvailableForTrading
979 )]
980 #[case(AxInstrumentState::Unknown, MarketStatusAction::NotAvailableForTrading)]
981 fn test_instrument_state_to_market_status_action(
982 #[case] state: AxInstrumentState,
983 #[case] expected: MarketStatusAction,
984 ) {
985 assert_eq!(MarketStatusAction::from(state), expected);
986 }
987
988 #[rstest]
989 #[case(AxOrderSide::Buy, "\"B\"")]
990 #[case(AxOrderSide::Sell, "\"S\"")]
991 fn test_order_side_serialization(#[case] side: AxOrderSide, #[case] expected: &str) {
992 let json = serde_json::to_string(&side).unwrap();
993 assert_eq!(json, expected);
994
995 let parsed: AxOrderSide = serde_json::from_str(&json).unwrap();
996 assert_eq!(parsed, side);
997 }
998
999 #[rstest]
1000 #[case("\"Buy\"")]
1001 #[case("\"Sell\"")]
1002 fn test_order_side_rejects_long_form(#[case] json: &str) {
1003 let error = serde_json::from_str::<AxOrderSide>(json).unwrap_err();
1004 assert_eq!(error.classify(), serde_json::error::Category::Data);
1005 }
1006
1007 #[rstest]
1008 #[case(AxOrderStatus::Pending, "\"PENDING\"")]
1009 #[case(AxOrderStatus::Accepted, "\"ACCEPTED\"")]
1010 #[case(AxOrderStatus::PartiallyFilled, "\"PARTIALLY_FILLED\"")]
1011 #[case(AxOrderStatus::Filled, "\"FILLED\"")]
1012 #[case(AxOrderStatus::Canceling, "\"CANCELING\"")]
1013 #[case(AxOrderStatus::Canceled, "\"CANCELED\"")]
1014 #[case(AxOrderStatus::Out, "\"OUT\"")]
1015 #[case(AxOrderStatus::ReconciledOut, "\"RECONCILED_OUT\"")]
1016 #[case(AxOrderStatus::Stale, "\"STALE\"")]
1017 fn test_order_status_serialization(#[case] status: AxOrderStatus, #[case] expected: &str) {
1018 let json = serde_json::to_string(&status).unwrap();
1019 assert_eq!(json, expected);
1020
1021 let parsed: AxOrderStatus = serde_json::from_str(&json).unwrap();
1022 assert_eq!(parsed, status);
1023 }
1024
1025 #[rstest]
1026 #[case(AxTimeInForce::Gtc, "\"GTC\"")]
1027 #[case(AxTimeInForce::Ioc, "\"IOC\"")]
1028 #[case(AxTimeInForce::Day, "\"DAY\"")]
1029 #[case(AxTimeInForce::Gtd, "\"GTD\"")]
1030 #[case(AxTimeInForce::Fok, "\"FOK\"")]
1031 #[case(AxTimeInForce::Ato, "\"ATO\"")]
1032 #[case(AxTimeInForce::Atc, "\"ATC\"")]
1033 fn test_time_in_force_serialization(#[case] tif: AxTimeInForce, #[case] expected: &str) {
1034 let json = serde_json::to_string(&tif).unwrap();
1035 assert_eq!(json, expected);
1036
1037 let parsed: AxTimeInForce = serde_json::from_str(&json).unwrap();
1038 assert_eq!(parsed, tif);
1039 }
1040
1041 #[rstest]
1042 #[case(AxMarketDataLevel::Level1, "\"LEVEL_1\"")]
1043 #[case(AxMarketDataLevel::Level2, "\"LEVEL_2\"")]
1044 #[case(AxMarketDataLevel::Level3, "\"LEVEL_3\"")]
1045 #[case(AxMarketDataLevel::Trades, "\"TRADES\"")]
1046 fn test_market_data_level_serialization(
1047 #[case] level: AxMarketDataLevel,
1048 #[case] expected: &str,
1049 ) {
1050 let json = serde_json::to_string(&level).unwrap();
1051 assert_eq!(json, expected);
1052
1053 let parsed: AxMarketDataLevel = serde_json::from_str(&json).unwrap();
1054 assert_eq!(parsed, level);
1055 }
1056
1057 #[rstest]
1058 #[case(AxCandleWidth::Seconds1, "\"1s\"")]
1059 #[case(AxCandleWidth::Minutes1, "\"1m\"")]
1060 #[case(AxCandleWidth::Minutes5, "\"5m\"")]
1061 #[case(AxCandleWidth::Hours1, "\"1h\"")]
1062 #[case(AxCandleWidth::Days1, "\"1d\"")]
1063 fn test_candle_width_serialization(#[case] width: AxCandleWidth, #[case] expected: &str) {
1064 let json = serde_json::to_string(&width).unwrap();
1065 assert_eq!(json, expected);
1066
1067 let parsed: AxCandleWidth = serde_json::from_str(&json).unwrap();
1068 assert_eq!(parsed, width);
1069 }
1070
1071 #[rstest]
1072 #[case(AxMdWsMessageType::Heartbeat, "\"h\"")]
1073 #[case(AxMdWsMessageType::Ticker, "\"s\"")]
1074 #[case(AxMdWsMessageType::Trade, "\"t\"")]
1075 #[case(AxMdWsMessageType::Candle, "\"c\"")]
1076 #[case(AxMdWsMessageType::BookLevel1, "\"1\"")]
1077 #[case(AxMdWsMessageType::BookLevel2, "\"2\"")]
1078 #[case(AxMdWsMessageType::BookLevel3, "\"3\"")]
1079 fn test_md_ws_message_type_serialization(
1080 #[case] msg_type: AxMdWsMessageType,
1081 #[case] expected: &str,
1082 ) {
1083 let json = serde_json::to_string(&msg_type).unwrap();
1084 assert_eq!(json, expected);
1085
1086 let parsed: AxMdWsMessageType = serde_json::from_str(&json).unwrap();
1087 assert_eq!(parsed, msg_type);
1088 }
1089
1090 #[rstest]
1091 #[case(AxOrderWsMessageType::Heartbeat, "\"h\"")]
1092 #[case(AxOrderWsMessageType::OrderAcknowledged, "\"n\"")]
1093 #[case(AxOrderWsMessageType::OrderCanceled, "\"c\"")]
1094 #[case(AxOrderWsMessageType::OrderFilled, "\"f\"")]
1095 #[case(AxOrderWsMessageType::OrderPartiallyFilled, "\"p\"")]
1096 fn test_order_ws_message_type_serialization(
1097 #[case] msg_type: AxOrderWsMessageType,
1098 #[case] expected: &str,
1099 ) {
1100 let json = serde_json::to_string(&msg_type).unwrap();
1101 assert_eq!(json, expected);
1102
1103 let parsed: AxOrderWsMessageType = serde_json::from_str(&json).unwrap();
1104 assert_eq!(parsed, msg_type);
1105 }
1106
1107 #[rstest]
1108 #[case(AxMdRequestType::Subscribe, "\"subscribe\"")]
1109 #[case(AxMdRequestType::Unsubscribe, "\"unsubscribe\"")]
1110 #[case(AxMdRequestType::SubscribeCandles, "\"subscribe_candles\"")]
1111 #[case(AxMdRequestType::UnsubscribeCandles, "\"unsubscribe_candles\"")]
1112 fn test_md_request_type_serialization(
1113 #[case] request_type: AxMdRequestType,
1114 #[case] expected: &str,
1115 ) {
1116 let json = serde_json::to_string(&request_type).unwrap();
1117 assert_eq!(json, expected);
1118
1119 let parsed: AxMdRequestType = serde_json::from_str(&json).unwrap();
1120 assert_eq!(parsed, request_type);
1121 }
1122
1123 #[rstest]
1124 #[case(AxOrderRequestType::PlaceOrder, "\"p\"")]
1125 #[case(AxOrderRequestType::CancelOrder, "\"x\"")]
1126 #[case(AxOrderRequestType::GetOpenOrders, "\"o\"")]
1127 fn test_order_request_type_serialization(
1128 #[case] request_type: AxOrderRequestType,
1129 #[case] expected: &str,
1130 ) {
1131 let json = serde_json::to_string(&request_type).unwrap();
1132 assert_eq!(json, expected);
1133
1134 let parsed: AxOrderRequestType = serde_json::from_str(&json).unwrap();
1135 assert_eq!(parsed, request_type);
1136 }
1137
1138 #[rstest]
1139 #[case("\"fx\"", AxCategory::Fx)]
1140 #[case("\"equities\"", AxCategory::Equities)]
1141 #[case("\"metals\"", AxCategory::Metals)]
1142 #[case("\"energy\"", AxCategory::Energy)]
1143 #[case("\"energy_etfs\"", AxCategory::EnergyEtfs)]
1144 #[case("\"treasuries\"", AxCategory::Treasuries)]
1145 #[case("\"compute\"", AxCategory::Compute)]
1146 #[case("\"crypto\"", AxCategory::Crypto)]
1147 #[case("\"something_new\"", AxCategory::Unknown)]
1148 fn test_category_deserialization(#[case] json: &str, #[case] expected: AxCategory) {
1149 let parsed: AxCategory = serde_json::from_str(json).unwrap();
1150 assert_eq!(parsed, expected);
1151 }
1152
1153 #[rstest]
1154 #[case(AxCategory::Fx, AssetClass::FX)]
1155 #[case(AxCategory::Equities, AssetClass::Equity)]
1156 #[case(AxCategory::EnergyEtfs, AssetClass::Equity)]
1157 #[case(AxCategory::Metals, AssetClass::Commodity)]
1158 #[case(AxCategory::Energy, AssetClass::Commodity)]
1159 #[case(AxCategory::Crypto, AssetClass::Cryptocurrency)]
1160 #[case(AxCategory::Treasuries, AssetClass::Debt)]
1161 #[case(AxCategory::Compute, AssetClass::Alternative)]
1162 #[case(AxCategory::Unknown, AssetClass::Alternative)]
1163 fn test_category_asset_class_mapping(
1164 #[case] category: AxCategory,
1165 #[case] expected: AssetClass,
1166 ) {
1167 assert_eq!(AssetClass::from(category), expected);
1168 }
1169}