1use nautilus_core::{
22 UnixNanos,
23 serialization::{
24 deserialize_optional_decimal_str, serialize_decimal_as_str,
25 serialize_optional_decimal_as_str,
26 },
27};
28use nautilus_model::{
29 data::{Bar, Data, OrderBookDeltas},
30 events::{
31 OrderAccepted, OrderCancelRejected, OrderCanceled, OrderExpired, OrderFilled, OrderRejected,
32 },
33 identifiers::{ClientOrderId, InstrumentId, StrategyId, TraderId, VenueOrderId},
34 reports::{FillReport, OrderStatusReport},
35 types::Currency,
36};
37use rust_decimal::Decimal;
38use serde::{Deserialize, Serialize};
39use ustr::Ustr;
40
41use super::error::AxWsErrorResponse;
42use crate::common::{
43 enums::{
44 AxCancelReason, AxCancelRejectionReason, AxCandleWidth, AxMarketDataLevel, AxMdRequestType,
45 AxOrderRequestType, AxOrderSide, AxOrderStatus, AxOrderType, AxOrderWsMessageType,
46 AxTimeInForce,
47 },
48 parse::{deserialize_decimal_or_zero, deserialize_optional_decimal_or_zero},
49};
50
51#[derive(Debug, Clone)]
56pub enum NautilusDataWsMessage {
57 Data(Vec<Data>),
59 Deltas(OrderBookDeltas),
61 Bar(Bar),
63 Heartbeat,
65 Error(AxWsError),
67 Reconnected,
69}
70
71#[derive(Debug, Clone)]
76pub enum NautilusExecWsMessage {
77 OrderAccepted(OrderAccepted),
79 OrderFilled(Box<OrderFilled>),
81 OrderCanceled(OrderCanceled),
83 OrderExpired(OrderExpired),
85 OrderRejected(OrderRejected),
87 OrderCancelRejected(OrderCancelRejected),
89 OrderStatusReports(Vec<OrderStatusReport>),
91 FillReports(Vec<FillReport>),
93}
94
95#[derive(Clone, Debug, Serialize, Deserialize)]
100pub struct AxMdSubscribe {
101 pub rid: i64,
103 #[serde(rename = "type")]
105 pub msg_type: AxMdRequestType,
106 pub symbol: Ustr,
108 pub level: AxMarketDataLevel,
110}
111
112#[derive(Clone, Debug, Serialize, Deserialize)]
117pub struct AxMdUnsubscribe {
118 pub rid: i64,
120 #[serde(rename = "type")]
122 pub msg_type: AxMdRequestType,
123 pub symbol: Ustr,
125}
126
127#[derive(Clone, Debug, Serialize, Deserialize)]
132pub struct AxMdSubscribeCandles {
133 pub rid: i64,
135 #[serde(rename = "type")]
137 pub msg_type: AxMdRequestType,
138 pub symbol: Ustr,
140 pub width: AxCandleWidth,
142}
143
144#[derive(Clone, Debug, Serialize, Deserialize)]
149pub struct AxMdUnsubscribeCandles {
150 pub rid: i64,
152 #[serde(rename = "type")]
154 pub msg_type: AxMdRequestType,
155 pub symbol: Ustr,
157 pub width: AxCandleWidth,
159}
160
161#[derive(Clone, Debug, Serialize, Deserialize)]
166pub struct AxMdHeartbeat {
167 pub ts: i64,
169 pub tn: i64,
171}
172
173#[derive(Clone, Debug)]
177pub enum AxMdMessage {
178 BookL1(AxMdBookL1),
179 BookL2(AxMdBookL2),
180 BookL3(AxMdBookL3),
181 Ticker(AxMdTicker),
182 Trade(AxMdTrade),
183 Candle(AxMdCandle),
184 Heartbeat(AxMdHeartbeat),
185 SubscriptionResponse(AxMdSubscriptionResponse),
187 Error(AxWsError),
188}
189
190#[derive(Clone, Debug, Deserialize)]
192pub struct AxMdSubscriptionResponse {
193 pub rid: i64,
195 pub result: AxMdSubscriptionResult,
197}
198
199#[derive(Clone, Debug, Deserialize)]
201pub struct AxMdSubscriptionResult {
202 #[serde(default)]
204 pub subscribed: Option<String>,
205 #[serde(default)]
207 pub subscribed_candle: Option<String>,
208 #[serde(default)]
210 pub unsubscribed: Option<String>,
211 #[serde(default)]
213 pub unsubscribed_candle: Option<String>,
214}
215
216#[derive(Clone, Debug, Deserialize)]
218pub struct AxMdErrorResponse {
219 pub rid: Option<i64>,
221 pub error: AxMdErrorInner,
223}
224
225#[derive(Clone, Debug, Deserialize)]
227pub struct AxMdErrorInner {
228 pub code: i32,
230 pub message: String,
232}
233
234impl From<AxMdErrorResponse> for AxWsError {
235 fn from(resp: AxMdErrorResponse) -> Self {
236 Self {
237 code: Some(resp.error.code.to_string()),
238 message: resp.error.message,
239 request_id: resp.rid,
240 }
241 }
242}
243
244#[derive(Clone, Debug, Serialize, Deserialize)]
249pub struct AxMdTicker {
250 pub ts: i64,
252 pub tn: i64,
254 pub s: Ustr,
256 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
258 pub p: Decimal,
259 pub q: u64,
261 #[serde(deserialize_with = "deserialize_optional_decimal_or_zero")]
263 pub o: Decimal,
264 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
266 pub l: Decimal,
267 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
269 pub h: Decimal,
270 pub v: u64,
272 #[serde(default)]
274 pub oi: Option<i64>,
275}
276
277#[derive(Clone, Debug, Serialize, Deserialize)]
282pub struct AxMdTrade {
283 pub ts: i64,
285 pub tn: i64,
287 pub s: Ustr,
289 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
291 pub p: Decimal,
292 pub q: u64,
294 #[serde(default)]
296 pub d: Option<AxOrderSide>,
297}
298
299#[derive(Clone, Debug, Serialize, Deserialize)]
304pub struct AxMdCandle {
305 pub symbol: Ustr,
307 pub ts: i64,
309 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
311 pub open: Decimal,
312 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
314 pub low: Decimal,
315 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
317 pub high: Decimal,
318 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
320 pub close: Decimal,
321 pub volume: u64,
323 pub buy_volume: u64,
325 pub sell_volume: u64,
327 pub width: AxCandleWidth,
329}
330
331#[derive(Clone, Debug, Serialize, Deserialize)]
333pub struct AxBookLevel {
334 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
336 pub p: Decimal,
337 pub q: u64,
339}
340
341#[derive(Clone, Debug, Serialize, Deserialize)]
343pub struct AxBookLevelL3 {
344 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
346 pub p: Decimal,
347 pub q: u64,
349 pub o: Vec<u64>,
351}
352
353#[derive(Clone, Debug, Serialize, Deserialize)]
358pub struct AxMdBookL1 {
359 pub ts: i64,
361 pub tn: i64,
363 pub s: Ustr,
365 pub b: Vec<AxBookLevel>,
367 pub a: Vec<AxBookLevel>,
369}
370
371#[derive(Clone, Debug, Serialize, Deserialize)]
376pub struct AxMdBookL2 {
377 pub ts: i64,
379 pub tn: i64,
381 pub s: Ustr,
383 pub b: Vec<AxBookLevel>,
385 pub a: Vec<AxBookLevel>,
387}
388
389#[derive(Clone, Debug, Serialize, Deserialize)]
394pub struct AxMdBookL3 {
395 pub ts: i64,
397 pub tn: i64,
399 pub s: Ustr,
401 pub b: Vec<AxBookLevelL3>,
403 pub a: Vec<AxBookLevelL3>,
405}
406
407#[derive(Clone, Debug, Serialize, Deserialize)]
412pub struct AxWsPlaceOrder {
413 pub rid: i64,
415 pub t: AxOrderRequestType,
417 pub s: Ustr,
419 pub d: AxOrderSide,
421 pub q: u64,
423 #[serde(
425 serialize_with = "serialize_decimal_as_str",
426 deserialize_with = "deserialize_decimal_or_zero"
427 )]
428 pub p: Decimal,
429 pub tif: AxTimeInForce,
431 pub po: bool,
433 #[serde(skip_serializing_if = "Option::is_none")]
435 pub cid: Option<u64>,
436 #[serde(skip_serializing_if = "Option::is_none")]
438 pub tag: Option<String>,
439 #[serde(skip_serializing_if = "Option::is_none")]
441 pub order_type: Option<AxOrderType>,
442 #[serde(
444 skip_serializing_if = "Option::is_none",
445 serialize_with = "serialize_optional_decimal_as_str",
446 deserialize_with = "deserialize_optional_decimal_str",
447 default
448 )]
449 pub trigger_price: Option<Decimal>,
450}
451
452#[derive(Clone, Debug, Serialize, Deserialize)]
457pub struct AxWsCancelOrder {
458 pub rid: i64,
460 pub t: AxOrderRequestType,
462 pub oid: String,
464}
465
466#[derive(Clone, Debug, Serialize, Deserialize)]
471pub struct AxWsGetOpenOrders {
472 pub rid: i64,
474 pub t: AxOrderRequestType,
476}
477
478#[derive(Clone, Debug, Serialize, Deserialize)]
483pub struct AxWsPlaceOrderResponse {
484 pub rid: i64,
486 pub res: AxWsPlaceOrderResult,
488}
489
490#[derive(Clone, Debug, Serialize, Deserialize)]
492pub struct AxWsPlaceOrderResult {
493 pub oid: String,
495}
496
497#[derive(Clone, Debug, Serialize, Deserialize)]
502pub struct AxWsCancelOrderResponse {
503 pub rid: i64,
505 pub res: AxWsCancelOrderResult,
507}
508
509#[derive(Clone, Debug, Serialize, Deserialize)]
511pub struct AxWsCancelOrderResult {
512 pub cxl_rx: bool,
514}
515
516#[derive(Clone, Debug, Serialize, Deserialize)]
521pub struct AxWsOpenOrdersResponse {
522 pub rid: i64,
524 pub res: Vec<AxWsOrder>,
526}
527
528#[derive(Clone, Debug, Deserialize)]
532pub struct AxWsOrderErrorResponse {
533 pub rid: i64,
535 pub err: AxWsOrderError,
537}
538
539#[derive(Clone, Debug, Deserialize)]
541pub struct AxWsOrderError {
542 pub code: i64,
544 pub msg: String,
546}
547
548#[derive(Clone, Debug, Deserialize)]
552pub struct AxWsListResponse {
553 pub rid: i64,
555 pub res: AxWsListResult,
557}
558
559#[derive(Clone, Debug, Deserialize)]
561pub struct AxWsListResult {
562 pub li: String,
564 #[serde(default)]
566 pub o: Option<Vec<AxWsOrder>>,
567}
568
569#[derive(Clone, Debug, Serialize, Deserialize)]
571pub struct AxWsOrder {
572 pub oid: String,
574 pub u: String,
576 pub s: Ustr,
578 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
580 pub p: Decimal,
581 pub q: u64,
583 pub xq: u64,
585 pub rq: u64,
587 pub o: AxOrderStatus,
589 pub d: AxOrderSide,
591 pub tif: AxTimeInForce,
593 pub ts: i64,
595 pub tn: i64,
597 #[serde(default)]
599 pub cid: Option<u64>,
600 #[serde(default)]
602 pub tag: Option<String>,
603 #[serde(default)]
605 pub txt: Option<String>,
606}
607
608#[derive(Clone, Debug, Serialize, Deserialize)]
613pub struct AxWsHeartbeat {
614 pub t: AxOrderWsMessageType,
616 pub ts: i64,
618 pub tn: i64,
620}
621
622#[derive(Clone, Debug, Serialize, Deserialize)]
627pub struct AxWsOrderAcknowledged {
628 pub ts: i64,
630 pub tn: i64,
632 pub eid: String,
634 pub o: AxWsOrder,
636}
637
638#[derive(Clone, Debug, Serialize, Deserialize)]
640pub struct AxWsTradeExecution {
641 pub tid: String,
643 pub s: Ustr,
645 pub q: u64,
647 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
649 pub p: Decimal,
650 pub d: AxOrderSide,
652 pub agg: bool,
654}
655
656#[derive(Clone, Debug, Serialize, Deserialize)]
661pub struct AxWsOrderPartiallyFilled {
662 pub ts: i64,
664 pub tn: i64,
666 pub eid: String,
668 pub o: AxWsOrder,
670 pub xs: AxWsTradeExecution,
672}
673
674#[derive(Clone, Debug, Serialize, Deserialize)]
679pub struct AxWsOrderFilled {
680 pub ts: i64,
682 pub tn: i64,
684 pub eid: String,
686 pub o: AxWsOrder,
688 pub xs: AxWsTradeExecution,
690}
691
692#[derive(Clone, Debug, Serialize, Deserialize)]
697pub struct AxWsOrderCanceled {
698 pub ts: i64,
700 pub tn: i64,
702 pub eid: String,
704 pub o: AxWsOrder,
706 pub xr: AxCancelReason,
708 #[serde(default)]
710 pub txt: Option<String>,
711}
712
713#[derive(Clone, Debug, Serialize, Deserialize)]
718pub struct AxWsOrderRejected {
719 pub ts: i64,
721 pub tn: i64,
723 pub eid: String,
725 pub o: AxWsOrder,
727 #[serde(default)]
729 pub r: Option<String>,
730 #[serde(default)]
732 pub txt: Option<String>,
733}
734
735#[derive(Clone, Debug, Serialize, Deserialize)]
740pub struct AxWsOrderExpired {
741 pub ts: i64,
743 pub tn: i64,
745 pub eid: String,
747 pub o: AxWsOrder,
749}
750
751#[derive(Clone, Debug, Serialize, Deserialize)]
756pub struct AxWsOrderReplaced {
757 pub ts: i64,
759 pub tn: i64,
761 pub eid: String,
763 pub o: AxWsOrder,
765}
766
767#[derive(Clone, Debug, Serialize, Deserialize)]
772pub struct AxWsOrderDoneForDay {
773 pub ts: i64,
775 pub tn: i64,
777 pub eid: String,
779 pub o: AxWsOrder,
781}
782
783#[derive(Clone, Debug, Serialize, Deserialize)]
788pub struct AxWsCancelRejected {
789 pub ts: i64,
791 pub tn: i64,
793 pub oid: String,
795 pub r: AxCancelRejectionReason,
797 #[serde(default)]
799 pub txt: Option<String>,
800}
801
802#[derive(Debug, Clone, Deserialize)]
807#[serde(tag = "t")]
808pub(crate) enum AxWsOrderEvent {
809 #[serde(rename = "h")]
811 Heartbeat,
812 #[serde(rename = "n")]
814 Acknowledged(AxWsOrderAcknowledged),
815 #[serde(rename = "p")]
817 PartiallyFilled(AxWsOrderPartiallyFilled),
818 #[serde(rename = "f")]
820 Filled(AxWsOrderFilled),
821 #[serde(rename = "c")]
823 Canceled(AxWsOrderCanceled),
824 #[serde(rename = "j")]
826 Rejected(AxWsOrderRejected),
827 #[serde(rename = "x")]
829 Expired(AxWsOrderExpired),
830 #[serde(rename = "r")]
832 Replaced(AxWsOrderReplaced),
833 #[serde(rename = "d")]
835 DoneForDay(AxWsOrderDoneForDay),
836 #[serde(rename = "e")]
838 CancelRejected(AxWsCancelRejected),
839}
840
841#[derive(Debug, Clone)]
845pub(crate) enum AxWsOrderResponse {
846 PlaceOrder(AxWsPlaceOrderResponse),
848 CancelOrder(AxWsCancelOrderResponse),
850 OpenOrders(AxWsOpenOrdersResponse),
852 List(AxWsListResponse),
854}
855
856#[derive(Debug, Clone)]
858pub(crate) enum AxWsRawMessage {
859 Error(AxWsOrderErrorResponse),
861 Response(AxWsOrderResponse),
863 Event(Box<AxWsOrderEvent>),
865}
866
867#[derive(Debug, Clone)]
871pub enum AxOrdersWsMessage {
872 Nautilus(NautilusExecWsMessage),
874 PlaceOrderResponse(AxWsPlaceOrderResponse),
876 CancelOrderResponse(AxWsCancelOrderResponse),
878 OpenOrdersResponse(AxWsOpenOrdersResponse),
880 Error(AxWsError),
882 Reconnected,
884 Authenticated,
886}
887
888#[derive(Debug, Clone)]
890pub struct AxWsError {
891 pub code: Option<String>,
893 pub message: String,
895 pub request_id: Option<i64>,
897}
898
899impl AxWsError {
900 #[must_use]
902 pub fn new(message: impl Into<String>) -> Self {
903 Self {
904 code: None,
905 message: message.into(),
906 request_id: None,
907 }
908 }
909
910 #[must_use]
912 pub fn with_code(code: impl Into<String>, message: impl Into<String>) -> Self {
913 Self {
914 code: Some(code.into()),
915 message: message.into(),
916 request_id: None,
917 }
918 }
919}
920
921impl From<AxWsOrderErrorResponse> for AxWsError {
922 fn from(resp: AxWsOrderErrorResponse) -> Self {
923 Self {
924 code: Some(resp.err.code.to_string()),
925 message: resp.err.msg,
926 request_id: Some(resp.rid),
927 }
928 }
929}
930
931impl From<AxWsErrorResponse> for AxWsError {
932 fn from(resp: AxWsErrorResponse) -> Self {
933 Self {
934 code: resp.code,
935 message: resp.message.unwrap_or_else(|| "Unknown error".to_string()),
936 request_id: resp.rid,
937 }
938 }
939}
940
941#[derive(Debug, Clone)]
945pub struct OrderMetadata {
946 pub trader_id: TraderId,
948 pub strategy_id: StrategyId,
950 pub instrument_id: InstrumentId,
952 pub client_order_id: ClientOrderId,
954 pub venue_order_id: Option<VenueOrderId>,
956 pub ts_init: UnixNanos,
958 pub size_precision: u8,
960 pub price_precision: u8,
962 pub quote_currency: Currency,
964}
965
966#[cfg(test)]
967mod tests {
968 use rstest::rstest;
969 use rust_decimal_macros::dec;
970
971 use super::{
972 super::parse::{parse_md_message, parse_order_message},
973 *,
974 };
975
976 #[rstest]
977 fn test_md_subscribe_serialization() {
978 let msg = AxMdSubscribe {
979 rid: 2,
980 msg_type: AxMdRequestType::Subscribe,
981 symbol: Ustr::from("BTCUSD-PERP"),
982 level: AxMarketDataLevel::Level2,
983 };
984 let json = serde_json::to_string(&msg).unwrap();
985 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
986
987 assert_eq!(parsed["rid"], 2);
988 assert_eq!(parsed["type"], "subscribe");
989 assert_eq!(parsed["symbol"], "BTCUSD-PERP");
990 assert_eq!(parsed["level"], "LEVEL_2");
991 }
992
993 #[rstest]
994 fn test_md_unsubscribe_serialization() {
995 let msg = AxMdUnsubscribe {
996 rid: 3,
997 msg_type: AxMdRequestType::Unsubscribe,
998 symbol: Ustr::from("BTCUSD-PERP"),
999 };
1000 let json = serde_json::to_string(&msg).unwrap();
1001 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1002
1003 assert_eq!(parsed["rid"], 3);
1004 assert_eq!(parsed["type"], "unsubscribe");
1005 assert_eq!(parsed["symbol"], "BTCUSD-PERP");
1006 }
1007
1008 #[rstest]
1009 fn test_md_subscribe_candles_serialization() {
1010 let msg = AxMdSubscribeCandles {
1011 rid: 4,
1012 msg_type: AxMdRequestType::SubscribeCandles,
1013 symbol: Ustr::from("BTCUSD-PERP"),
1014 width: AxCandleWidth::Minutes1,
1015 };
1016 let json = serde_json::to_string(&msg).unwrap();
1017 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1018
1019 assert_eq!(parsed["rid"], 4);
1020 assert_eq!(parsed["type"], "subscribe_candles");
1021 assert_eq!(parsed["symbol"], "BTCUSD-PERP");
1022 assert_eq!(parsed["width"], "1m");
1023 }
1024
1025 #[rstest]
1026 fn test_md_unsubscribe_candles_serialization() {
1027 let msg = AxMdUnsubscribeCandles {
1028 rid: 5,
1029 msg_type: AxMdRequestType::UnsubscribeCandles,
1030 symbol: Ustr::from("BTCUSD-PERP"),
1031 width: AxCandleWidth::Minutes1,
1032 };
1033 let json = serde_json::to_string(&msg).unwrap();
1034 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1035
1036 assert_eq!(parsed["rid"], 5);
1037 assert_eq!(parsed["type"], "unsubscribe_candles");
1038 assert_eq!(parsed["symbol"], "BTCUSD-PERP");
1039 assert_eq!(parsed["width"], "1m");
1040 }
1041
1042 #[rstest]
1043 fn test_ws_place_order_serialization() {
1044 let msg = AxWsPlaceOrder {
1045 rid: 1,
1046 t: AxOrderRequestType::PlaceOrder,
1047 s: Ustr::from("BTCUSD-PERP"),
1048 d: AxOrderSide::Buy,
1049 q: 100,
1050 p: dec!(50000.50),
1051 tif: AxTimeInForce::Gtc,
1052 po: false,
1053 tag: Some("Nautilus".to_string()),
1054 cid: Some(1234567890),
1055 order_type: None,
1056 trigger_price: None,
1057 };
1058
1059 let json = serde_json::to_string(&msg).unwrap();
1060 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1061
1062 assert_eq!(parsed["rid"], 1);
1063 assert_eq!(parsed["t"], "p");
1064 assert_eq!(parsed["s"], "BTCUSD-PERP");
1065 assert_eq!(parsed["d"], "B");
1066 assert_eq!(parsed["q"], 100);
1067 assert_eq!(parsed["p"], "50000.50");
1068 assert_eq!(parsed["tif"], "GTC");
1069 assert_eq!(parsed["po"], false);
1070 assert_eq!(parsed["tag"], "Nautilus");
1071 assert_eq!(parsed["cid"], 1234567890);
1072 assert!(parsed.get("order_type").is_none());
1073 assert!(parsed.get("trigger_price").is_none());
1074 }
1075
1076 #[rstest]
1077 fn test_ws_place_stop_loss_order_serialization() {
1078 let msg = AxWsPlaceOrder {
1079 rid: 2,
1080 t: AxOrderRequestType::PlaceOrder,
1081 s: Ustr::from("BTCUSD-PERP"),
1082 d: AxOrderSide::Sell,
1083 q: 50,
1084 p: dec!(48000.00),
1085 tif: AxTimeInForce::Gtc,
1086 po: false,
1087 tag: None,
1088 cid: None,
1089 order_type: Some(AxOrderType::StopLossLimit),
1090 trigger_price: Some(dec!(49000.00)),
1091 };
1092
1093 let json = serde_json::to_string(&msg).unwrap();
1094 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1095
1096 assert_eq!(parsed["rid"], 2);
1097 assert_eq!(parsed["order_type"], "STOP_LOSS_LIMIT");
1098 assert_eq!(parsed["trigger_price"], "49000.00");
1099 }
1100
1101 #[rstest]
1102 fn test_ws_cancel_order_serialization() {
1103 let msg = AxWsCancelOrder {
1104 rid: 2,
1105 t: AxOrderRequestType::CancelOrder,
1106 oid: "O-01ARZ3NDEKTSV4RRFFQ69G5FAV".to_string(),
1107 };
1108 let json = serde_json::to_string(&msg).unwrap();
1109 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1110
1111 assert_eq!(parsed["rid"], 2);
1112 assert_eq!(parsed["t"], "x");
1113 assert_eq!(parsed["oid"], "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1114 }
1115
1116 #[rstest]
1117 fn test_ws_get_open_orders_serialization() {
1118 let msg = AxWsGetOpenOrders {
1119 rid: 3,
1120 t: AxOrderRequestType::GetOpenOrders,
1121 };
1122 let json = serde_json::to_string(&msg).unwrap();
1123 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1124
1125 assert_eq!(parsed["rid"], 3);
1126 assert_eq!(parsed["t"], "o");
1127 }
1128
1129 #[rstest]
1130 fn test_load_md_heartbeat_from_file() {
1131 let json = include_str!("../../test_data/ws_md_heartbeat.json");
1132 let msg = parse_md_message(json).unwrap();
1133 assert!(matches!(msg, AxMdMessage::Heartbeat(_)));
1134 }
1135
1136 #[rstest]
1137 fn test_load_md_ticker_from_file() {
1138 let json = include_str!("../../test_data/ws_md_ticker.json");
1139 let msg: AxMdTicker = serde_json::from_str(json).unwrap();
1140 assert_eq!(msg.s.as_str(), "BTCUSD-PERP");
1141 }
1142
1143 #[rstest]
1144 fn test_load_md_trade_from_file() {
1145 let json = include_str!("../../test_data/ws_md_trade.json");
1146 let msg: AxMdTrade = serde_json::from_str(json).unwrap();
1147 assert_eq!(msg.d, Some(AxOrderSide::Buy));
1148 }
1149
1150 #[rstest]
1151 fn test_load_md_candle_from_file() {
1152 let json = include_str!("../../test_data/ws_md_candle.json");
1153 let msg: AxMdCandle = serde_json::from_str(json).unwrap();
1154 assert_eq!(msg.width, AxCandleWidth::Minutes1);
1155 }
1156
1157 #[rstest]
1158 fn test_load_md_book_l1_from_file() {
1159 let json = include_str!("../../test_data/ws_md_book_l1.json");
1160 let msg: AxMdBookL1 = serde_json::from_str(json).unwrap();
1161 assert_eq!(msg.b.len(), 1);
1162 assert_eq!(msg.a.len(), 1);
1163 }
1164
1165 #[rstest]
1166 fn test_load_md_book_l2_from_file() {
1167 let json = include_str!("../../test_data/ws_md_book_l2.json");
1168 let msg: AxMdBookL2 = serde_json::from_str(json).unwrap();
1169 assert_eq!(msg.b.len(), 3);
1170 assert_eq!(msg.a.len(), 3);
1171 }
1172
1173 #[rstest]
1174 fn test_load_md_book_l3_from_file() {
1175 let json = include_str!("../../test_data/ws_md_book_l3.json");
1176 let msg: AxMdBookL3 = serde_json::from_str(json).unwrap();
1177 assert_eq!(msg.b.len(), 2);
1178 assert!(!msg.b[0].o.is_empty());
1179 }
1180
1181 #[rstest]
1182 fn test_load_order_place_response_from_file() {
1183 let json = include_str!("../../test_data/ws_order_place_response.json");
1184 let msg: AxWsPlaceOrderResponse = serde_json::from_str(json).unwrap();
1185 assert_eq!(msg.res.oid, "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1186 }
1187
1188 #[rstest]
1189 fn test_load_order_cancel_response_from_file() {
1190 let json = include_str!("../../test_data/ws_order_cancel_response.json");
1191 let msg: AxWsCancelOrderResponse = serde_json::from_str(json).unwrap();
1192 assert!(msg.res.cxl_rx);
1193 }
1194
1195 #[rstest]
1196 fn test_load_order_open_orders_response_from_file() {
1197 let json = include_str!("../../test_data/ws_order_open_orders_response.json");
1198 let msg: AxWsOpenOrdersResponse = serde_json::from_str(json).unwrap();
1199 assert_eq!(msg.res.len(), 1);
1200 }
1201
1202 #[rstest]
1203 fn test_load_order_heartbeat_from_file() {
1204 let json = include_str!("../../test_data/ws_order_heartbeat.json");
1205 let msg: AxWsHeartbeat = serde_json::from_str(json).unwrap();
1206 assert_eq!(msg.ts, 1609459200);
1207 }
1208
1209 #[rstest]
1210 fn test_load_order_acknowledged_from_file() {
1211 let json = include_str!("../../test_data/ws_order_acknowledged.json");
1212 let msg: AxWsOrderAcknowledged = serde_json::from_str(json).unwrap();
1213 assert_eq!(msg.o.oid, "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1214 }
1215
1216 #[rstest]
1217 fn test_load_order_filled_from_file() {
1218 let json = include_str!("../../test_data/ws_order_filled.json");
1219 let msg: AxWsOrderFilled = serde_json::from_str(json).unwrap();
1220 assert_eq!(msg.o.o, AxOrderStatus::Filled);
1221 }
1222
1223 #[rstest]
1224 fn test_load_order_partially_filled_from_file() {
1225 let json = include_str!("../../test_data/ws_order_partially_filled.json");
1226 let msg: AxWsOrderPartiallyFilled = serde_json::from_str(json).unwrap();
1227 assert_eq!(msg.xs.q, 50);
1228 }
1229
1230 #[rstest]
1231 fn test_load_order_canceled_from_file() {
1232 let json = include_str!("../../test_data/ws_order_canceled.json");
1233 let msg: AxWsOrderCanceled = serde_json::from_str(json).unwrap();
1234 assert_eq!(msg.xr, AxCancelReason::UserRequested);
1235 }
1236
1237 #[rstest]
1238 fn test_load_order_rejected_from_file() {
1239 let json = include_str!("../../test_data/ws_order_rejected.json");
1240 let msg: AxWsOrderRejected = serde_json::from_str(json).unwrap();
1241 assert_eq!(msg.r, Some("INSUFFICIENT_MARGIN".to_string()));
1242 }
1243
1244 #[rstest]
1245 fn test_load_order_expired_from_file() {
1246 let json = include_str!("../../test_data/ws_order_expired.json");
1247 let msg: AxWsOrderExpired = serde_json::from_str(json).unwrap();
1248 assert_eq!(msg.o.tif, AxTimeInForce::Ioc);
1249 }
1250
1251 #[rstest]
1252 fn test_load_order_replaced_from_file() {
1253 let json = include_str!("../../test_data/ws_order_replaced.json");
1254 let msg: AxWsOrderReplaced = serde_json::from_str(json).unwrap();
1255 assert_eq!(msg.o.p, dec!(50500.00));
1256 }
1257
1258 #[rstest]
1259 fn test_load_order_done_for_day_from_file() {
1260 let json = include_str!("../../test_data/ws_order_done_for_day.json");
1261 let msg: AxWsOrderDoneForDay = serde_json::from_str(json).unwrap();
1262 assert_eq!(msg.o.xq, 50);
1263 }
1264
1265 #[rstest]
1266 fn test_load_cancel_rejected_from_file() {
1267 let json = include_str!("../../test_data/ws_cancel_rejected.json");
1268 let msg: AxWsCancelRejected = serde_json::from_str(json).unwrap();
1269 assert_eq!(msg.r, AxCancelRejectionReason::OrderNotFound);
1270 }
1271
1272 #[rstest]
1273 fn test_load_order_error_response_from_file() {
1274 let json = include_str!("../../test_data/ws_order_error_response.json");
1275 let msg: AxWsOrderErrorResponse = serde_json::from_str(json).unwrap();
1276 assert_eq!(msg.rid, 1);
1277 assert_eq!(msg.err.code, 400);
1278 assert!(msg.err.msg.contains("initial margin"));
1279 }
1280
1281 #[rstest]
1282 fn test_load_order_list_response_from_file() {
1283 let json = include_str!("../../test_data/ws_order_list_response.json");
1284 let msg: AxWsListResponse = serde_json::from_str(json).unwrap();
1285 assert_eq!(msg.rid, 0);
1286 assert_eq!(msg.res.li, "01KCQM-4WP1-0000");
1287 assert!(msg.res.o.is_none());
1288 }
1289
1290 #[rstest]
1291 fn test_load_order_list_response_with_orders_from_file() {
1292 let json = include_str!("../../test_data/ws_order_list_response_with_orders.json");
1293 let msg: AxWsListResponse = serde_json::from_str(json).unwrap();
1294 assert_eq!(msg.rid, 0);
1295 assert_eq!(msg.res.li, "01KCQM-4WP1-0000");
1296 let orders = msg.res.o.unwrap();
1297 assert_eq!(orders.len(), 2);
1298 assert_eq!(orders[0].oid, "O-01KF4QM3VVJEDH98ZVNS1PCSBB");
1299 assert_eq!(orders[1].oid, "O-01KF4QM3K9FJZWYA02JF9Y1FJA");
1300 }
1301
1302 #[rstest]
1303 fn test_raw_message_error_variant() {
1304 let json = include_str!("../../test_data/ws_order_error_response.json");
1305 let msg = parse_order_message(json).unwrap();
1306 assert!(matches!(msg, AxWsRawMessage::Error(_)));
1307 }
1308
1309 #[rstest]
1310 fn test_raw_message_list_response_variant() {
1311 let json = include_str!("../../test_data/ws_order_list_response.json");
1312 let msg = parse_order_message(json).unwrap();
1313 assert!(matches!(
1314 msg,
1315 AxWsRawMessage::Response(AxWsOrderResponse::List(_))
1316 ));
1317 }
1318
1319 #[rstest]
1320 fn test_raw_message_event_variant() {
1321 let json = include_str!("../../test_data/ws_order_acknowledged.json");
1322 let msg = parse_order_message(json).unwrap();
1323 assert!(matches!(
1324 msg,
1325 AxWsRawMessage::Event(ref e) if matches!(**e, AxWsOrderEvent::Acknowledged(_))
1326 ));
1327 }
1328
1329 #[rstest]
1330 fn test_raw_message_place_response_variant() {
1331 let json = include_str!("../../test_data/ws_order_place_response.json");
1332 let msg = parse_order_message(json).unwrap();
1333 assert!(matches!(
1334 msg,
1335 AxWsRawMessage::Response(AxWsOrderResponse::PlaceOrder(_))
1336 ));
1337 }
1338
1339 #[rstest]
1340 fn test_raw_message_cancel_response_variant() {
1341 let json = include_str!("../../test_data/ws_order_cancel_response.json");
1342 let msg = parse_order_message(json).unwrap();
1343 assert!(matches!(
1344 msg,
1345 AxWsRawMessage::Response(AxWsOrderResponse::CancelOrder(_))
1346 ));
1347 }
1348
1349 #[rstest]
1350 fn test_raw_message_open_orders_response_variant() {
1351 let json = include_str!("../../test_data/ws_order_open_orders_response.json");
1352 let msg = parse_order_message(json).unwrap();
1353 assert!(matches!(
1354 msg,
1355 AxWsRawMessage::Response(AxWsOrderResponse::OpenOrders(_))
1356 ));
1357 }
1358}