crypto_ws_client/clients/mexc/
mexc_swap.rs1use async_trait::async_trait;
2use std::collections::HashMap;
3use tokio_tungstenite::tungstenite::Message;
4
5use super::EXCHANGE_NAME;
6use crate::{
7 clients::common_traits::{
8 Candlestick, Level3OrderBook, OrderBook, OrderBookTopK, Ticker, Trade, BBO,
9 },
10 common::{
11 command_translator::CommandTranslator,
12 message_handler::{MessageHandler, MiscMessage},
13 ws_client_internal::WSClientInternal,
14 },
15 WSClient,
16};
17
18use log::*;
19use serde_json::Value;
20
21pub(super) const SWAP_WEBSOCKET_URL: &str = "wss://contract.mexc.com/ws";
22
23pub struct MexcSwapWSClient {
28 client: WSClientInternal<MexcMessageHandler>,
29 translator: MexcCommandTranslator,
30}
31
32impl_new_constructor!(
33 MexcSwapWSClient,
34 EXCHANGE_NAME,
35 SWAP_WEBSOCKET_URL,
36 MexcMessageHandler {},
37 MexcCommandTranslator {}
38);
39
40#[rustfmt::skip]
41impl_trait!(Trade, MexcSwapWSClient, subscribe_trade, "deal");
42#[rustfmt::skip]
43impl_trait!(Ticker, MexcSwapWSClient, subscribe_ticker, "ticker");
44#[rustfmt::skip]
45impl_trait!(OrderBook, MexcSwapWSClient, subscribe_orderbook, "depth");
46#[rustfmt::skip]
47impl_trait!(OrderBookTopK, MexcSwapWSClient, subscribe_orderbook_topk, "depth.full");
48impl_candlestick!(MexcSwapWSClient);
49
50panic_bbo!(MexcSwapWSClient);
51panic_l3_orderbook!(MexcSwapWSClient);
52
53impl_ws_client_trait!(MexcSwapWSClient);
54
55struct MexcMessageHandler {}
56struct MexcCommandTranslator {}
57
58impl MessageHandler for MexcMessageHandler {
59 fn handle_message(&mut self, msg: &str) -> MiscMessage {
60 let obj = serde_json::from_str::<HashMap<String, Value>>(msg).unwrap();
61 if obj.contains_key("channel") && obj.contains_key("data") && obj.contains_key("ts") {
62 let channel = obj.get("channel").unwrap().as_str().unwrap();
63 match channel {
64 "pong" => MiscMessage::Pong,
65 "rs.error" => {
66 error!("Received {} from {}", msg, EXCHANGE_NAME);
67 panic!("Received {msg} from {EXCHANGE_NAME}");
68 }
69 _ => {
70 if obj.contains_key("symbol") && channel.starts_with("push.") {
71 MiscMessage::Normal
72 } else {
73 info!("Received {} from {}", msg, EXCHANGE_NAME);
74 MiscMessage::Other
75 }
76 }
77 }
78 } else {
79 error!("Received {} from {}", msg, SWAP_WEBSOCKET_URL);
80 MiscMessage::Other
81 }
82 }
83
84 fn get_ping_msg_and_interval(&self) -> Option<(Message, u64)> {
85 Some((Message::Text(r#"{"method":"ping"}"#.to_string()), 60))
87 }
88}
89
90impl MexcCommandTranslator {
91 fn topic_to_command(channel: &str, symbol: &str, subscribe: bool) -> String {
92 format!(
93 r#"{{"method":"{}.{}","param":{{"symbol":"{}"}}}}"#,
94 if subscribe { "sub" } else { "unsub" },
95 channel,
96 symbol
97 )
98 }
99
100 fn interval_to_string(interval: usize) -> String {
101 let tmp = match interval {
102 60 => "Min1",
103 300 => "Min5",
104 900 => "Min15",
105 1800 => "Min30",
106 3600 => "Min60",
107 14400 => "Hour4",
108 28800 => "Hour8",
109 86400 => "Day1",
110 604800 => "Week1",
111 2592000 => "Month1",
112 _ => panic!(
113 "MEXC has intervals Min1,Min5,Min15,Min30,Min60,Hour4,Hour8,Day1,Week1,Month1"
114 ),
115 };
116 tmp.to_string()
117 }
118}
119
120impl CommandTranslator for MexcCommandTranslator {
121 fn translate_to_commands(&self, subscribe: bool, topics: &[(String, String)]) -> Vec<String> {
122 topics
123 .iter()
124 .map(|(channel, symbol)| {
125 MexcCommandTranslator::topic_to_command(channel, symbol, subscribe)
126 })
127 .collect()
128 }
129
130 fn translate_to_candlestick_commands(
131 &self,
132 subscribe: bool,
133 symbol_interval_list: &[(String, usize)],
134 ) -> Vec<String> {
135 symbol_interval_list
136 .iter()
137 .map(|(symbol, interval)| {
138 format!(
139 r#"{{"method":"{}.kline","param":{{"symbol":"{}","interval":"{}"}}}}"#,
140 if subscribe { "sub" } else { "unsub" },
141 symbol,
142 Self::interval_to_string(*interval)
143 )
144 })
145 .collect()
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use crate::common::command_translator::CommandTranslator;
152
153 #[test]
154 fn test_one_topic() {
155 let translator = super::MexcCommandTranslator {};
156 let commands =
157 translator.translate_to_commands(true, &[("deal".to_string(), "BTC_USDT".to_string())]);
158
159 assert_eq!(1, commands.len());
160 assert_eq!(r#"{"method":"sub.deal","param":{"symbol":"BTC_USDT"}}"#, commands[0]);
161 }
162
163 #[test]
164 fn test_two_topic() {
165 let translator = super::MexcCommandTranslator {};
166 let commands = translator.translate_to_commands(
167 true,
168 &[
169 ("deal".to_string(), "BTC_USDT".to_string()),
170 ("depth".to_string(), "ETH_USDT".to_string()),
171 ],
172 );
173
174 assert_eq!(2, commands.len());
175 assert_eq!(r#"{"method":"sub.deal","param":{"symbol":"BTC_USDT"}}"#, commands[0]);
176 assert_eq!(r#"{"method":"sub.depth","param":{"symbol":"ETH_USDT"}}"#, commands[1]);
177 }
178
179 #[test]
180 fn test_candlestick() {
181 let translator = super::MexcCommandTranslator {};
182 let commands =
183 translator.translate_to_candlestick_commands(true, &[("BTC_USDT".to_string(), 60)]);
184
185 assert_eq!(1, commands.len());
186 assert_eq!(
187 r#"{"method":"sub.kline","param":{"symbol":"BTC_USDT","interval":"Min1"}}"#,
188 commands[0]
189 );
190 }
191}