Repository: hummingbot/hummingbot
Pull request: Fix/mexc modify python connector
Python-only implementation. Refer to Ndax.
Usage of NetworkMockingAssistant and aioresponses module for unit testing. Refer to Ndax unit tests.
Single websocket connection for public websocket channels. Refer to NdaxAPIOrderBookDataSource.listen_for_order_book_diffs or
CryptoComAPIOrderBookDataSource
Shared aiohttp.ClientSession obj across all components.
Integrate AsyncThrottler into Mexc connector.

Problem statement:
Implement changes to MEXC connector
Before the December release, MEXC connector should be updated to conform with the standards of integrating a new connector into our codebase.

The following changes needs to be made by the MEXC team:

1. Python-only implementation. Refer to Ndax.
2. Usage of `NetworkMockingAssistant` and `aioresponses` module for unit testing. Refer to Ndax unit tests.
3. Single websocket connection for public websocket channels. Refer to `HuobiAPIOrderBookDataSource` (`HuobiAPIOrderBookDataSource.listen_for_order_book_diffs`)
4. Shared `WebAssistantsFactory` obj across all components. Refer to Huobi
5. Integrate `AsyncThrottler` into Mexc connector.

Hints from issue discussion:

Commit message: check f-string flake8 error

Patch to review:
diff --git a/hummingbot/connector/exchange/mexc/constants.py b/hummingbot/connector/exchange/mexc/constants.py
deleted file mode 100644
index 32ace8e84d0..00000000000
--- a/hummingbot/connector/exchange/mexc/constants.py
+++ /dev/null
@@ -1,21 +0,0 @@
-
-# URLs
-
-MEXC_BASE_URL = "https://www.mexc.com"
-
-MEXC_SYMBOL_URL = '/open/api/v2/market/symbols'
-MEXC_TICKERS_URL = '/open/api/v2/market/ticker'
-MEXC_DEPTH_URL = '/open/api/v2/market/depth?symbol={trading_pair}&depth=200'
-MEXC_PRICE_URL = '/open/api/v2/market/ticker?symbol={trading_pair}'
-MEXC_PING_URL = '/open/api/v2/common/ping'
-
-
-MEXC_PLACE_ORDER = "/open/api/v2/order/place"
-MEXC_ORDER_DETAILS_URL = '/open/api/v2/order/query'
-MEXC_ORDER_CANCEL = '/open/api/v2/order/cancel'
-MEXC_BATCH_ORDER_CANCEL = '/open/api/v2/order/cancel'
-MEXC_BALANCE_URL = '/open/api/v2/account/info'
-MEXC_DEAL_DETAIL = '/open/api/v2/order/deal_detail'
-
-# WS
-MEXC_WS_URL_PUBLIC = 'wss://wbs.mexc.com/raw/ws'
diff --git a/hummingbot/connector/exchange/mexc/mexc_api_order_book_data_source.py b/hummingbot/connector/exchange/mexc/mexc_api_order_book_data_source.py
index 4610954bd49..da8f3da62bd 100644
--- a/hummingbot/connector/exchange/mexc/mexc_api_order_book_data_source.py
+++ b/hummingbot/connector/exchange/mexc/mexc_api_order_book_data_source.py
@@ -2,94 +2,110 @@
 import aiohttp
 import aiohttp.client_ws
 import asyncio
-
-import json
 import logging
 import pandas as pd
 import time
 from typing import (
     Any,
-    AsyncIterable,
     Dict,
     List,
     Optional,
 )
-from websockets.exceptions import ConnectionClosed
-from hummingbot.connector.exchange.mexc import mexc_utils
+
 from hummingbot.connector.exchange.mexc.mexc_utils import (
     convert_from_exchange_trading_pair,
-    convert_to_exchange_trading_pair
+    convert_to_exchange_trading_pair,
+    microseconds,
 )
 from hummingbot.core.data_type.order_book_message import OrderBookMessage
 from hummingbot.core.data_type.order_book import OrderBook
 from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource
 from hummingbot.logger import HummingbotLogger
 from hummingbot.connector.exchange.mexc.mexc_order_book import MexcOrderBook
-from hummingbot.connector.exchange.mexc.constants import (
-    MEXC_SYMBOL_URL,
-    MEXC_DEPTH_URL,
-    MEXC_TICKERS_URL,
-    MEXC_WS_URL_PUBLIC, MEXC_BASE_URL,
-)
+from hummingbot.connector.exchange.mexc import mexc_constants as CONSTANTS
 from dateutil.parser import parse as dateparse
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
+from hummingbot.connector.exchange.mexc.mexc_websocket_adaptor import MexcWebSocketAdaptor
+from collections import defaultdict
 
 
 class MexcAPIOrderBookDataSource(OrderBookTrackerDataSource):
     MESSAGE_TIMEOUT = 120.0
     PING_TIMEOUT = 10.0
 
-    _mexcaobds_logger: Optional[HummingbotLogger] = None
+    _logger: Optional[HummingbotLogger] = None
+
+    def __init__(self, trading_pairs: List[str],
+                 shared_client: Optional[aiohttp.ClientSession] = None,
+                 throttler: Optional[AsyncThrottler] = None, ):
+        super().__init__(trading_pairs)
+        self._trading_pairs: List[str] = trading_pairs
+        self._throttler = throttler or self._get_throttler_instance()
+        self._shared_client = shared_client or self._get_session_instance()
+        self._message_queue: Dict[str, asyncio.Queue] = defaultdict(asyncio.Queue)
 
     @classmethod
     def logger(cls) -> HummingbotLogger:
-        if cls._mexcaobds_logger is None:
-            cls._mexcaobds_logger = logging.getLogger(__name__)
-        return cls._mexcaobds_logger
+        if cls._logger is None:
+            cls._logger = logging.getLogger(__name__)
+        return cls._logger
 
-    def __init__(self, trading_pairs: List[str]):
-        super().__init__(trading_pairs)
-        self._trading_pairs: List[str] = trading_pairs
+    @classmethod
+    def _get_session_instance(cls) -> aiohttp.ClientSession:
+        session = aiohttp.ClientSession()
+        return session
+
+    @classmethod
+    def _get_throttler_instance(cls) -> AsyncThrottler:
+        throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS)
+        return throttler
 
     @staticmethod
     async def fetch_trading_pairs() -> List[str]:
         async with aiohttp.ClientSession() as client:
-            url = MEXC_BASE_URL + MEXC_SYMBOL_URL
-            async with client.get(url) as products_response:
-
-                products_response: aiohttp.ClientResponse = products_response
-                if products_response.status != 200:
-                    raise IOError(f"Error fetching active MEXC. HTTP status is {products_response.status}.")
-
-                data = await products_response.json()
-                data = data['data']
-
-                trading_pairs = []
-                for item in data:
-                    if item['state'] == "ENABLED":
-                        trading_pairs.append(convert_from_exchange_trading_pair(item["symbol"]))
+            throttler = MexcAPIOrderBookDataSource._get_throttler_instance()
+            async with throttler.execute_task(CONSTANTS.MEXC_SYMBOL_URL):
+                url = CONSTANTS.MEXC_BASE_URL + CONSTANTS.MEXC_SYMBOL_URL
+                async with client.get(url) as products_response:
+
+                    products_response: aiohttp.ClientResponse = products_response
+                    if products_response.status != 200:
+                        return []
+                        # raise IOError(f"Error fetching active MEXC. HTTP status is {products_response.status}.")
+
+                    data = await products_response.json()
+                    data = data['data']
+
+                    trading_pairs = []
+                    for item in data:
+                        if item['state'] == "ENABLED":
+                            trading_pairs.append(convert_from_exchange_trading_pair(item["symbol"]))
         return trading_pairs
 
     async def get_new_order_book(self, trading_pair: str) -> OrderBook:
-        async with aiohttp.ClientSession() as client:
-            snapshot: Dict[str, Any] = await self.get_snapshot(client, trading_pair)
+        snapshot: Dict[str, Any] = await self.get_snapshot(self._shared_client, trading_pair)
 
-            snapshot_msg: OrderBookMessage = MexcOrderBook.snapshot_message_from_exchange(
-                snapshot,
-                trading_pair,
-                timestamp=mexc_utils.microseconds(),
-                metadata={"trading_pair": trading_pair})
-            order_book: OrderBook = self.order_book_create_function()
-            order_book.apply_snapshot(snapshot_msg.bids, snapshot_msg.asks, snapshot_msg.update_id)
-            return order_book
+        snapshot_msg: OrderBookMessage = MexcOrderBook.snapshot_message_from_exchange(
+            snapshot,
+            trading_pair,
+            timestamp=microseconds(),
+            metadata={"trading_pair": trading_pair})
+        order_book: OrderBook = self.order_book_create_function()
+        order_book.apply_snapshot(snapshot_msg.bids, snapshot_msg.asks, snapshot_msg.update_id)
+        return order_book
 
     @classmethod
-    async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, float]:
-        async with aiohttp.ClientSession() as client:
-            url = MEXC_BASE_URL + MEXC_TICKERS_URL
+    async def get_last_traded_prices(cls, trading_pairs: List[str], throttler: Optional[AsyncThrottler] = None,
+                                     shared_client: Optional[aiohttp.ClientSession] = None) -> Dict[str, float]:
+        client = shared_client or cls._get_session_instance()
+        throttler = throttler or cls._get_throttler_instance()
+        async with throttler.execute_task(CONSTANTS.MEXC_TICKERS_URL):
+            url = CONSTANTS.MEXC_BASE_URL + CONSTANTS.MEXC_TICKERS_URL
             async with client.get(url) as products_response:
                 products_response: aiohttp.ClientResponse = products_response
                 if products_response.status != 200:
-                    raise IOError(f"Error get tickers from MEXC markets. HTTP status is {products_response.status}.")
+                    # raise IOError(f"Error get tickers from MEXC markets. HTTP status is {products_response.status}.")
+                    return {}
                 data = await products_response.json()
                 data = data['data']
                 all_markets: pd.DataFrame = pd.DataFrame.from_records(data=data)
@@ -116,165 +132,149 @@ async def get_trading_pairs(self) -> List[str]:
         return self._trading_pairs
 
     @staticmethod
-    async def get_snapshot(client: aiohttp.ClientSession, trading_pair: str) -> Dict[str, Any]:
-        trading_pair = convert_to_exchange_trading_pair(trading_pair)
-        tick_url = MEXC_DEPTH_URL.format(trading_pair=trading_pair)
-        url = MEXC_BASE_URL + tick_url
-        async with client.get(url) as response:
-            response: aiohttp.ClientResponse = response
-            if response.status != 200:
-                raise IOError(f"Error fetching MEXC market snapshot for {trading_pair}. "
-                              f"HTTP status is {response.status}.")
-            api_data = await response.read()
-            data: Dict[str, Any] = json.loads(api_data)['data']
-            data['ts'] = mexc_utils.microseconds()
-
-            return data
+    async def get_snapshot(client: aiohttp.ClientSession, trading_pair: str,
+                           throttler: Optional[AsyncThrottler] = None) -> Dict[str, Any]:
+        throttler = throttler or MexcAPIOrderBookDataSource._get_throttler_instance()
+        async with throttler.execute_task(CONSTANTS.MEXC_DEPTH_URL):
+            trading_pair = convert_to_exchange_trading_pair(trading_pair)
+            tick_url = CONSTANTS.MEXC_DEPTH_URL.format(trading_pair=trading_pair)
+            url = CONSTANTS.MEXC_BASE_URL + tick_url
+            async with client.get(url) as response:
+                response: aiohttp.ClientResponse = response
+                status = response.status
+                if status != 200:
+                    raise IOError(f"Error fetching MEXC market snapshot for {trading_pair}. "
+                                  f"HTTP status is {status}.")
+                api_data = await response.json()
+                data = api_data['data']
+                data['ts'] = microseconds()
+
+                return data
 
     @classmethod
     def iso_to_timestamp(cls, date: str):
         return dateparse(date).timestamp()
 
-    async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue):
-
+    async def _create_websocket_connection(self) -> MexcWebSocketAdaptor:
+        """
+        Initialize WebSocket client for UserStreamDataSource
+        """
+        try:
+            ws = MexcWebSocketAdaptor(throttler=self._throttler, shared_client=self._shared_client)
+            await ws.connect()
+            return ws
+        except asyncio.CancelledError:
+            raise
+        except Exception as e:
+            self.logger().network(f"Unexpected error occured connecting to {CONSTANTS.EXCHANGE_NAME} WebSocket API. "
+                                  f"({e})")
+            raise
+
+    async def listen_for_subscriptions(self):
+        ws = None
         while True:
             try:
-                trading_pairs: List[str] = self._trading_pairs
-                session = aiohttp.ClientSession()
-                async with session.ws_connect(MEXC_WS_URL_PUBLIC) as ws:
-                    ws: aiohttp.client_ws.ClientWebSocketResponse = ws
-
-                    for trading_pair in trading_pairs:
-                        trading_pair = convert_to_exchange_trading_pair(trading_pair)
-                        subscribe_request: Dict[str, Any] = {
-                            "op": "sub.deal",
-                            "symbol": trading_pair,
-                        }
-                        await ws.send_str(json.dumps(subscribe_request))
+                ws = await self._create_websocket_connection()
+                await ws.subscribe_to_order_book_streams(self._trading_pairs)
 
-                    async for raw_msg in self._inner_messages(ws):
-                        decoded_msg: dict = raw_msg
+                async for msg in ws.iter_messages():
+                    decoded_msg: dict = msg
 
-                        self.logger().debug("decode menssae:" + str(decoded_msg))
-
-                        if 'channel' in decoded_msg.keys() and decoded_msg['channel'] == 'push.deal':
-                            self.logger().debug(f"Recived new trade: {decoded_msg}")
-
-                            for data in decoded_msg['data']['deals']:
-                                trading_pair = convert_from_exchange_trading_pair(decoded_msg['symbol'])
-                                trade_message: OrderBookMessage = MexcOrderBook.trade_message_from_exchange(
-                                    data, data['t'], metadata={"trading_pair": trading_pair}
-                                )
-                                self.logger().debug(f'Putting msg in queue: {str(trade_message)}')
-                                output.put_nowait(trade_message)
-                        else:
-                            self.logger().debug(f"Unrecognized message received from MEXC websocket: {decoded_msg}")
+                    if 'channel' in decoded_msg.keys() and decoded_msg['channel'] in MexcWebSocketAdaptor.SUBSCRIPTION_LIST:
+                        self._message_queue[decoded_msg['channel']].put_nowait(decoded_msg)
+                    else:
+                        self.logger().debug(f"Unrecognized message received from MEXC websocket: {decoded_msg}")
+            except asyncio.CancelledError:
+                raise
+            except Exception:
+                self.logger().error(
+                    "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...",
+                    exc_info=True,
+                )
+                await self._sleep(5.0)
+            finally:
+                ws and await ws.disconnect()
 
+    async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue):
+        msg_queue = self._message_queue[MexcWebSocketAdaptor.DEAL_CHANNEL_ID]
+        while True:
+            try:
+                decoded_msg = await msg_queue.get()
+                self.logger().debug(f"Recived new trade: {decoded_msg}")
+
+                for data in decoded_msg['data']['deals']:
+                    trading_pair = convert_from_exchange_trading_pair(decoded_msg['symbol'])
+                    trade_message: OrderBookMessage = MexcOrderBook.trade_message_from_exchange(
+                        data, data['t'], metadata={"trading_pair": trading_pair}
+                    )
+                    self.logger().debug(f'Putting msg in queue: {str(trade_message)}')
+                    output.put_nowait(trade_message)
             except asyncio.CancelledError:
                 raise
             except Exception:
                 self.logger().error("Unexpected error with WebSocket connection ,Retrying after 30 seconds...",
                                     exc_info=True)
                 await asyncio.sleep(30.0)
-            finally:
-                await session.close()
-
-    async def _inner_messages(self,
-                              ws: aiohttp.ClientWebSocketResponse) -> AsyncIterable[str]:
-        try:
-            while True:
-                try:
-                    msg: str = await asyncio.wait_for(ws.receive_json(), timeout=self.MESSAGE_TIMEOUT)
-                    yield msg
-                except asyncio.TimeoutError:
-                    pong_waiter = ws.ping()
-                    self.logger().warning("WebSocket receive_json timeout ...")
-                    await asyncio.wait_for(pong_waiter, timeout=self.PING_TIMEOUT)
-        except asyncio.TimeoutError:
-            self.logger().warning("WebSocket ping timed out . Going to reconnect...")
-            return
-        except ConnectionClosed:
-            return
-        finally:
-            await ws.close()
 
     async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue):
+        msg_queue = self._message_queue[MexcWebSocketAdaptor.DEPTH_CHANNEL_ID]
         while True:
             try:
-                trading_pairs: List[str] = await self.get_trading_pairs()
-                session = aiohttp.ClientSession()
-                async with session.ws_connect(MEXC_WS_URL_PUBLIC) as ws:
-                    ws: aiohttp.client_ws.ClientWebSocketResponse = ws
-                    for trading_pair in trading_pairs:
-                        trading_pair = convert_to_exchange_trading_pair(trading_pair)
-                        subscribe_request: Dict[str, Any] = {
-                            "op": "sub.depth",
-                            "symbol": trading_pair,
+                decoded_msg = await msg_queue.get()
+                if decoded_msg['data'].get('asks'):
+                    asks = [
+                        {
+                            'price': ask['p'],
+                            'quantity': ask['q']
                         }
-                        await ws.send_str(json.dumps(subscribe_request))
-
-                    async for raw_msg in self._inner_messages(ws):
-
-                        decoded_msg: dict = raw_msg
+                        for ask in decoded_msg["data"]["asks"]]
+                    decoded_msg['data']['asks'] = asks
+                if decoded_msg['data'].get('bids'):
+                    bids = [
+                        {
+                            'price': bid['p'],
+                            'quantity': bid['q']
+                        }
+                        for bid in decoded_msg["data"]["bids"]]
+                    decoded_msg['data']['bids'] = bids
+                order_book_message: OrderBookMessage = MexcOrderBook.diff_message_from_exchange(
+                    decoded_msg['data'], microseconds(),
+                    metadata={"trading_pair": convert_from_exchange_trading_pair(decoded_msg['symbol'])}
+                )
+                output.put_nowait(order_book_message)
 
-                        if 'channel' in decoded_msg.keys() and decoded_msg['channel'] == 'push.depth':
-                            if decoded_msg['data'].get('asks'):
-                                asks = [
-                                    {
-                                        'price': ask['p'],
-                                        'quantity': ask['q']
-                                    }
-                                    for ask in decoded_msg["data"]["asks"]]
-                                decoded_msg['data']['asks'] = asks
-                            if decoded_msg['data'].get('bids'):
-                                bids = [
-                                    {
-                                        'price': bid['p'],
-                                        'quantity': bid['q']
-                                    }
-                                    for bid in decoded_msg["data"]["bids"]]
-                                decoded_msg['data']['bids'] = bids
-                            order_book_message: OrderBookMessage = MexcOrderBook.diff_message_from_exchange(
-                                decoded_msg['data'], mexc_utils.microseconds(),
-                                metadata={"trading_pair": convert_from_exchange_trading_pair(trading_pair)}
-                            )
-                            output.put_nowait(order_book_message)
-                        else:
-                            self.logger().debug(f"Unrecognized message received from MEXC websocket: {decoded_msg}")
             except asyncio.CancelledError:
                 raise
             except Exception:
                 self.logger().error("Unexpected error with WebSocket connection. Retrying after 30 seconds...",
                                     exc_info=True)
                 await asyncio.sleep(30.0)
-            finally:
-                await session.close()
 
     async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue):
         while True:
             try:
                 trading_pairs: List[str] = await self.get_trading_pairs()
-                async with aiohttp.ClientSession() as client:
-                    for trading_pair in trading_pairs:
-                        try:
-                            snapshot: Dict[str, Any] = await self.get_snapshot(client, trading_pair)
-                            snapshot_msg: OrderBookMessage = MexcOrderBook.snapshot_message_from_exchange(
-                                snapshot,
-                                trading_pair,
-                                timestamp=mexc_utils.microseconds(),
-                                metadata={"trading_pair": trading_pair})
-                            output.put_nowait(snapshot_msg)
-                            self.logger().debug(f"Saved order book snapshot for {trading_pair}")
-                            await asyncio.sleep(5.0)
-                        except asyncio.CancelledError:
-                            raise
-                        except Exception as ex:
-                            self.logger().error("Unexpected error." + repr(ex), exc_info=True)
-                            await asyncio.sleep(5.0)
-                    this_hour: pd.Timestamp = pd.Timestamp.utcnow().replace(minute=0, second=0, microsecond=0)
-                    next_hour: pd.Timestamp = this_hour + pd.Timedelta(hours=1)
-                    delta: float = next_hour.timestamp() - time.time()
-                    await asyncio.sleep(delta)
+                session = self._shared_client
+                for trading_pair in trading_pairs:
+                    try:
+                        snapshot: Dict[str, Any] = await self.get_snapshot(session, trading_pair)
+                        snapshot_msg: OrderBookMessage = MexcOrderBook.snapshot_message_from_exchange(
+                            snapshot,
+                            trading_pair,
+                            timestamp=microseconds(),
+                            metadata={"trading_pair": trading_pair})
+                        output.put_nowait(snapshot_msg)
+                        self.logger().debug(f"Saved order book snapshot for {trading_pair}")
+                        await asyncio.sleep(5.0)
+                    except asyncio.CancelledError:
+                        raise
+                    except Exception as ex:
+                        self.logger().error("Unexpected error." + repr(ex), exc_info=True)
+                        await asyncio.sleep(5.0)
+                this_hour: pd.Timestamp = pd.Timestamp.utcnow().replace(minute=0, second=0, microsecond=0)
+                next_hour: pd.Timestamp = this_hour + pd.Timedelta(hours=1)
+                delta: float = next_hour.timestamp() - time.time()
+                await asyncio.sleep(delta)
             except asyncio.CancelledError:
                 raise
             except Exception as ex1:
diff --git a/hummingbot/connector/exchange/mexc/mexc_api_user_stream_data_source.py b/hummingbot/connector/exchange/mexc/mexc_api_user_stream_data_source.py
index 6745b295900..f926fe70674 100644
--- a/hummingbot/connector/exchange/mexc/mexc_api_user_stream_data_source.py
+++ b/hummingbot/connector/exchange/mexc/mexc_api_user_stream_data_source.py
@@ -17,7 +17,8 @@
     Any
 )
 
-from hummingbot.connector.exchange.mexc.constants import MEXC_WS_URL_PUBLIC
+from hummingbot.connector.exchange.mexc import mexc_constants as CONSTANTS
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
 from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource
 from hummingbot.logger import HummingbotLogger
 from hummingbot.connector.exchange.mexc.mexc_auth import MexcAuth
@@ -28,25 +29,30 @@
 
 
 class MexcAPIUserStreamDataSource(UserStreamTrackerDataSource):
-    _mexcausds_logger: Optional[HummingbotLogger] = None
+    _logger: Optional[HummingbotLogger] = None
     MESSAGE_TIMEOUT = 300.0
 
     @classmethod
     def logger(cls) -> HummingbotLogger:
-        if cls._mexcausds_logger is None:
-            cls._mexcausds_logger = logging.getLogger(__name__)
+        if cls._logger is None:
+            cls._logger = logging.getLogger(__name__)
 
-        return cls._mexcausds_logger
+        return cls._logger
 
-    def __init__(self, mexc_auth: MexcAuth, trading_pairs: Optional[List[str]] = []):
-        self._current_listen_key = None
-        self._current_endpoint = None
-        self._listen_for_user_stram_task = None
+    def __init__(self, throttler: AsyncThrottler, mexc_auth: MexcAuth, trading_pairs: Optional[List[str]] = [],
+                 shared_client: Optional[aiohttp.ClientSession] = None):
+        self._shared_client = shared_client or self._get_session_instance()
         self._last_recv_time: float = 0
         self._auth: MexcAuth = mexc_auth
         self._trading_pairs = trading_pairs
+        self._throttler = throttler
         super().__init__()
 
+    @classmethod
+    def _get_session_instance(cls) -> aiohttp.ClientSession:
+        session = aiohttp.ClientSession()
+        return session
+
     @property
     def last_recv_time(self) -> float:
         return self._last_recv_time
@@ -56,11 +62,11 @@ async def _authenticate_client(self):
 
     async def listen_for_user_stream(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue):
         while True:
+            session = self._shared_client
             try:
-                session = aiohttp.ClientSession()
-                async with session.ws_connect(MEXC_WS_URL_PUBLIC) as ws:
-                    ws: aiohttp.client_ws.ClientWebSocketResponse = ws
-
+                ws = await session.ws_connect(CONSTANTS.MEXC_WS_URL_PUBLIC)
+                ws: aiohttp.client_ws.ClientWebSocketResponse = ws
+                try:
                     params: Dict[str, Any] = {
                         'api_key': self._auth.api_key,
                         "op": "sub.personal",
@@ -71,18 +77,22 @@ async def listen_for_user_stream(self, ev_loop: asyncio.BaseEventLoop, output: a
                     sign_data = hashlib.md5(params_sign.encode()).hexdigest()
                     del params['api_secret']
                     params["sign"] = sign_data
-
-                    await ws.send_str(json.dumps(params))
-
-                    async for raw_msg in self._inner_messages(ws):
-                        self._last_recv_time = time.time()
-                        decoded_msg: dict = raw_msg
-                        if 'channel' in decoded_msg.keys() and decoded_msg['channel'] == 'push.personal.order':
-                            output.put_nowait(decoded_msg)
-                        elif 'channel' in decoded_msg.keys() and decoded_msg['channel'] == 'sub.personal':
-                            pass
-                        else:
-                            self.logger().debug(f"other message received from MEXC websocket: {decoded_msg}")
+                    async with self._throttler.execute_task(CONSTANTS.MEXC_WS_URL_PUBLIC):
+                        await ws.send_str(json.dumps(params))
+
+                        async for raw_msg in self._inner_messages(ws):
+                            self._last_recv_time = time.time()
+                            decoded_msg: dict = raw_msg
+                            if 'channel' in decoded_msg.keys() and decoded_msg['channel'] == 'push.personal.order':
+                                output.put_nowait(decoded_msg)
+                            elif 'channel' in decoded_msg.keys() and decoded_msg['channel'] == 'sub.personal':
+                                pass
+                            else:
+                                self.logger().debug(f"other message received from MEXC websocket: {decoded_msg}")
+                except Exception as ex2:
+                    raise ex2
+                finally:
+                    await ws.close()
 
             except asyncio.CancelledError:
                 raise
@@ -90,18 +100,18 @@ async def listen_for_user_stream(self, ev_loop: asyncio.BaseEventLoop, output: a
                 self.logger().error("Unexpected error with WebSocket connection ,Retrying after 30 seconds..." + str(ex),
                                     exc_info=True)
                 await asyncio.sleep(30.0)
-            finally:
-                await session.close()
 
     async def _inner_messages(self,
                               ws: aiohttp.ClientWebSocketResponse) -> AsyncIterable[str]:
         try:
             while True:
-                msg: str = await asyncio.wait_for(ws.receive_json(), timeout=self.MESSAGE_TIMEOUT)
-                yield msg
+                msg = await asyncio.wait_for(ws.receive(), timeout=self.MESSAGE_TIMEOUT)
+                if msg.type == aiohttp.WSMsgType.CLOSED:
+                    raise ConnectionError
+                yield json.loads(msg.data)
         except asyncio.TimeoutError:
             return
         except ConnectionClosed:
             return
-        finally:
-            await ws.close()
+        except ConnectionError:
+            return
diff --git a/hummingbot/connector/exchange/mexc/mexc_constants.py b/hummingbot/connector/exchange/mexc/mexc_constants.py
new file mode 100644
index 00000000000..06e10586249
--- /dev/null
+++ b/hummingbot/connector/exchange/mexc/mexc_constants.py
@@ -0,0 +1,117 @@
+from hummingbot.core.api_throttler.data_types import RateLimit, LinkedLimitWeightPair
+
+EXCHANGE_NAME = "mexc"
+# URLs
+
+MEXC_BASE_URL = "https://www.mexc.com"
+
+MEXC_SYMBOL_URL = '/open/api/v2/market/symbols'
+MEXC_TICKERS_URL = '/open/api/v2/market/ticker'
+MEXC_DEPTH_URL = '/open/api/v2/market/depth?symbol={trading_pair}&depth=200'
+MEXC_PRICE_URL = '/open/api/v2/market/ticker?symbol={trading_pair}'
+MEXC_PING_URL = '/open/api/v2/common/ping'
+
+
+MEXC_PLACE_ORDER = "/open/api/v2/order/place"
+MEXC_ORDER_DETAILS_URL = '/open/api/v2/order/query'
+MEXC_ORDER_CANCEL = '/open/api/v2/order/cancel'
+MEXC_BATCH_ORDER_CANCEL = '/open/api/v2/order/cancel'
+MEXC_BALANCE_URL = '/open/api/v2/account/info'
+MEXC_DEAL_DETAIL = '/open/api/v2/order/deal_detail'
+
+# WS
+MEXC_WS_URL_PUBLIC = 'wss://wbs.mexc.com/raw/ws'
+
+MINUTE = 1
+SECOND_MINUTE = 2
+HTTP_ENDPOINTS_LIMIT_ID = "AllHTTP"
+HTTP_LIMIT = 20
+WS_AUTH_LIMIT_ID = "AllWsAuth"
+WS_ENDPOINTS_LIMIT_ID = "AllWs"
+WS_LIMIT = 20
+
+RATE_LIMITS = [
+    RateLimit(
+        limit_id=HTTP_ENDPOINTS_LIMIT_ID,
+        limit=HTTP_LIMIT,
+        time_interval=MINUTE
+    ),
+    # public http
+    RateLimit(
+        limit_id=MEXC_SYMBOL_URL,
+        limit=HTTP_LIMIT,
+        time_interval=SECOND_MINUTE,
+        linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)],
+    ),
+    RateLimit(
+        limit_id=MEXC_TICKERS_URL,
+        limit=HTTP_LIMIT,
+        time_interval=MINUTE,
+        linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)],
+    ),
+    RateLimit(
+        limit_id=MEXC_DEPTH_URL,
+        limit=HTTP_LIMIT,
+        time_interval=MINUTE,
+        linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)],
+    ),
+    # private http
+    RateLimit(
+        limit_id=MEXC_PRICE_URL,
+        limit=HTTP_LIMIT,
+        time_interval=MINUTE,
+        linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)],
+    ),
+    RateLimit(
+        limit_id=MEXC_PING_URL,
+        limit=HTTP_LIMIT,
+        time_interval=MINUTE,
+        linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)],
+    ),
+    RateLimit(
+        limit_id=MEXC_PLACE_ORDER,
+        limit=HTTP_LIMIT,
+        time_interval=MINUTE,
+        linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)],
+    ),
+    RateLimit(
+        limit_id=MEXC_ORDER_DETAILS_URL,
+        limit=HTTP_LIMIT,
+        time_interval=MINUTE,
+        linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)],
+    ),
+    RateLimit(
+        limit_id=MEXC_ORDER_CANCEL,
+        limit=HTTP_LIMIT,
+        time_interval=MINUTE,
+        linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)],
+    ),
+    RateLimit(
+        limit_id=MEXC_BATCH_ORDER_CANCEL,
+        limit=HTTP_LIMIT,
+        time_interval=MINUTE,
+        linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)],
+    ),
+    RateLimit(
+        limit_id=MEXC_BALANCE_URL,
+        limit=HTTP_LIMIT,
+        time_interval=MINUTE,
+        linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)],
+    ),
+    RateLimit(
+        limit_id=MEXC_DEAL_DETAIL,
+        limit=HTTP_LIMIT,
+        time_interval=MINUTE,
+        linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)],
+    ),
+    # ws public
+    RateLimit(limit_id=WS_AUTH_LIMIT_ID, limit=50, time_interval=MINUTE),
+    RateLimit(limit_id=WS_ENDPOINTS_LIMIT_ID, limit=WS_LIMIT, time_interval=MINUTE),
+    RateLimit(
+        limit_id=MEXC_WS_URL_PUBLIC,
+        limit=WS_LIMIT,
+        time_interval=MINUTE,
+        linked_limits=[LinkedLimitWeightPair(WS_ENDPOINTS_LIMIT_ID)],
+    ),
+
+]
diff --git a/hummingbot/connector/exchange/mexc/mexc_exchange.pxd b/hummingbot/connector/exchange/mexc/mexc_exchange.pxd
deleted file mode 100644
index 209cc0789f5..00000000000
--- a/hummingbot/connector/exchange/mexc/mexc_exchange.pxd
+++ /dev/null
@@ -1,33 +0,0 @@
-from hummingbot.connector.exchange_base cimport ExchangeBase
-from hummingbot.core.data_type.transaction_tracker cimport TransactionTracker
-
-cdef class MexcExchange(ExchangeBase):
-    cdef:
-        str _account_id
-        object _async_scheduler
-        object _data_source_type
-        object _ev_loop
-        object _mexc_auth
-        dict _in_flight_orders
-        double _last_poll_timestamp
-        double _last_timestamp
-        object _poll_notifier
-        double _poll_interval
-        object _shared_client
-        public object _status_polling_task
-        dict _trading_rules
-        object _user_stream_tracker
-        public object _trading_rules_polling_task
-        public object _user_stream_event_listener_task
-        public object _user_stream_tracker_task
-        TransactionTracker _tx_tracker
-
-    cdef c_did_timeout_tx(self, str tracking_id)
-    cdef c_start_tracking_order(self,
-                                str client_order_id,
-                                str exchange_order_id,
-                                str trading_pair,
-                                object order_type,
-                                object trade_type,
-                                object price,
-                                object amount)
diff --git a/hummingbot/connector/exchange/mexc/mexc_exchange.pyx b/hummingbot/connector/exchange/mexc/mexc_exchange.py
similarity index 57%
rename from hummingbot/connector/exchange/mexc/mexc_exchange.pyx
rename to hummingbot/connector/exchange/mexc/mexc_exchange.py
index 80edb5d9fe1..4f5405c1970 100644
--- a/hummingbot/connector/exchange/mexc/mexc_exchange.pyx
+++ b/hummingbot/connector/exchange/mexc/mexc_exchange.py
@@ -1,9 +1,7 @@
 import asyncio
 
 import aiohttp
-from libc.stdint cimport int64_t
 import logging
-import time
 from typing import (
     Any,
     AsyncIterable,
@@ -12,13 +10,13 @@
     Optional,
 )
 import ujson
-from hummingbot.core.clock cimport Clock
-from hummingbot.connector.exchange.mexc import mexc_utils
+
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
+from hummingbot.core.clock import Clock
 from hummingbot.core.data_type.cancellation_result import CancellationResult
 from hummingbot.core.data_type.limit_order import LimitOrder
-from hummingbot.core.data_type.order_book cimport OrderBook
+from hummingbot.core.data_type.order_book import OrderBook
 from hummingbot.core.data_type.order_book_tracker import OrderBookTrackerDataSourceType
-from hummingbot.core.data_type.transaction_tracker import TransactionTracker
 from hummingbot.core.event.events import (
     MarketEvent,
     BuyOrderCompletedEvent,
@@ -27,7 +25,6 @@
     OrderCancelledEvent,
     BuyOrderCreatedEvent,
     SellOrderCreatedEvent,
-    MarketTransactionFailureEvent,
     MarketOrderFailureEvent,
     OrderType,
     TradeType,
@@ -45,23 +42,22 @@
 from hummingbot.connector.exchange.mexc.mexc_auth import MexcAuth
 from hummingbot.connector.exchange.mexc.mexc_in_flight_order import MexcInFlightOrder
 from hummingbot.connector.exchange.mexc.mexc_order_book_tracker import MexcOrderBookTracker
-from hummingbot.connector.trading_rule cimport TradingRule
+from hummingbot.connector.trading_rule import TradingRule
 from hummingbot.connector.exchange_base import (
     ExchangeBase,
     s_decimal_NaN
 )
 from hummingbot.connector.exchange.mexc.mexc_user_stream_tracker import MexcUserStreamTracker
 from hummingbot.core.utils.tracking_nonce import get_tracking_nonce
-from hummingbot.core.utils.estimate_fee import estimate_fee
-
-from hummingbot.connector.exchange.mexc.constants import *
+from hummingbot.connector.exchange.mexc import mexc_constants as CONSTANTS
 
 from hummingbot.connector.exchange.mexc.mexc_utils import (
     convert_to_exchange_trading_pair,
+    num_to_increment,
     convert_from_exchange_trading_pair, ws_order_status_convert_to_str
 )
 
-from decimal import *
+from decimal import Decimal
 
 hm_logger = None
 s_decimal_0 = Decimal(0)
@@ -73,44 +69,30 @@ def __init__(self, error_payload: Dict[str, Any]):
         self.error_payload = error_payload
 
 
-cdef class MexcExchangeTransactionTracker(TransactionTracker):
-    cdef:
-        MexcExchange _owner
-
-    def __init__(self, owner: MexcExchange):
-        super().__init__()
-        self._owner = owner
-
-    cdef c_did_timeout_tx(self, str tx_id):
-        TransactionTracker.c_did_timeout_tx(self, tx_id)
-        self._owner.c_did_timeout_tx(tx_id)
-
-cdef class MexcExchange(ExchangeBase):
-    def stop_tracking_order(self, order_id: str):
-        pass
-
-    MARKET_RECEIVED_ASSET_EVENT_TAG = MarketEvent.ReceivedAsset.value
-    MARKET_BUY_ORDER_COMPLETED_EVENT_TAG = MarketEvent.BuyOrderCompleted.value
-    MARKET_SELL_ORDER_COMPLETED_EVENT_TAG = MarketEvent.SellOrderCompleted.value
-    MARKET_WITHDRAW_ASSET_EVENT_TAG = MarketEvent.WithdrawAsset.value
-    MARKET_ORDER_CANCELLED_EVENT_TAG = MarketEvent.OrderCancelled.value
-    MARKET_TRANSACTION_FAILURE_EVENT_TAG = MarketEvent.TransactionFailure.value
-    MARKET_ORDER_FAILURE_EVENT_TAG = MarketEvent.OrderFailure.value
-    MARKET_ORDER_FILLED_EVENT_TAG = MarketEvent.OrderFilled.value
-    MARKET_BUY_ORDER_CREATED_EVENT_TAG = MarketEvent.BuyOrderCreated.value
-    MARKET_SELL_ORDER_CREATED_EVENT_TAG = MarketEvent.SellOrderCreated.value
+class MexcExchange(ExchangeBase):
+    MARKET_RECEIVED_ASSET_EVENT_TAG = MarketEvent.ReceivedAsset
+    MARKET_BUY_ORDER_COMPLETED_EVENT_TAG = MarketEvent.BuyOrderCompleted
+    MARKET_SELL_ORDER_COMPLETED_EVENT_TAG = MarketEvent.SellOrderCompleted
+    MARKET_WITHDRAW_ASSET_EVENT_TAG = MarketEvent.WithdrawAsset
+    MARKET_ORDER_CANCELLED_EVENT_TAG = MarketEvent.OrderCancelled
+    MARKET_TRANSACTION_FAILURE_EVENT_TAG = MarketEvent.TransactionFailure
+    MARKET_ORDER_FAILURE_EVENT_TAG = MarketEvent.OrderFailure
+    MARKET_ORDER_FILLED_EVENT_TAG = MarketEvent.OrderFilled
+    MARKET_BUY_ORDER_CREATED_EVENT_TAG = MarketEvent.BuyOrderCreated
+    MARKET_SELL_ORDER_CREATED_EVENT_TAG = MarketEvent.SellOrderCreated
     API_CALL_TIMEOUT = 10.0
     UPDATE_ORDERS_INTERVAL = 10.0
     SHORT_POLL_INTERVAL = 5.0
     MORE_SHORT_POLL_INTERVAL = 1.0
     LONG_POLL_INTERVAL = 120.0
 
+    _logger = None
+
     @classmethod
     def logger(cls) -> HummingbotLogger:
-        global hm_logger
-        if hm_logger is None:
-            hm_logger = logging.getLogger(__name__)
-        return hm_logger
+        if cls._logger is None:
+            cls._logger = logging.getLogger(__name__)
+        return cls._logger
 
     def __init__(self,
                  mexc_api_key: str,
@@ -121,6 +103,8 @@ def __init__(self,
                  trading_required: bool = True):
 
         super().__init__()
+        self._throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS)
+        self._shared_client = aiohttp.ClientSession()
         self._async_scheduler = AsyncCallScheduler(call_interval=0.5)
         self._data_source_type = order_book_tracker_data_source_type
         self._ev_loop = asyncio.get_event_loop()
@@ -128,17 +112,20 @@ def __init__(self,
         self._in_flight_orders = {}
         self._last_poll_timestamp = 0
         self._last_timestamp = 0
-        self._order_book_tracker = MexcOrderBookTracker(trading_pairs=trading_pairs)
+        self._order_book_tracker = MexcOrderBookTracker(
+            throttler=self._throttler, trading_pairs=trading_pairs, shared_client=self._shared_client)
         self._poll_notifier = asyncio.Event()
         self._poll_interval = poll_interval
-        self._shared_client = None
         self._status_polling_task = None
         self._trading_required = trading_required
         self._trading_rules = {}
         self._trading_rules_polling_task = None
-        self._tx_tracker = MexcExchangeTransactionTracker(self)
-        self._user_stream_tracker = MexcUserStreamTracker(mexc_auth=self._mexc_auth,
-                                                          trading_pairs=trading_pairs)
+        self._user_stream_tracker = MexcUserStreamTracker(throttler=self._throttler,
+                                                          mexc_auth=self._mexc_auth,
+                                                          trading_pairs=trading_pairs,
+                                                          shared_client=self._shared_client)
+        self._user_stream_tracker_task = None
+        self._user_stream_event_listener_task = None
 
     @property
     def name(self) -> str:
@@ -170,8 +157,9 @@ def limit_orders(self) -> List[LimitOrder]:
     @property
     def tracking_states(self) -> Dict[str, Any]:
         return {
-            key: value.to_json()
-            for key, value in self._in_flight_orders.items()
+            client_oid: order.to_json()
+            for client_oid, order in self._in_flight_orders.items()
+            if not order.is_done
         }
 
     def restore_tracking_states(self, saved_states: Dict[str, Any]):
@@ -181,7 +169,7 @@ def restore_tracking_states(self, saved_states: Dict[str, Any]):
         })
 
     @property
-    def shared_client(self) -> str:
+    def shared_client(self) -> aiohttp.ClientSession:
         return self._shared_client
 
     @property
@@ -192,16 +180,25 @@ def user_stream_tracker(self) -> MexcUserStreamTracker:
     def shared_client(self, client: aiohttp.ClientSession):
         self._shared_client = client
 
-    cdef c_start(self, Clock clock, double timestamp):
-        self._tx_tracker.c_start(clock, timestamp)
-        ExchangeBase.c_start(self, clock, timestamp)
+    def start(self, clock: Clock, timestamp: float):
+        """
+        This function is called automatically by the clock.
+        """
+        super().start(clock, timestamp)
 
-    cdef c_stop(self, Clock clock):
-        ExchangeBase.c_stop(self, clock)
-        self._async_scheduler.stop()
+    def stop(self, clock: Clock):
+        """
+        This function is called automatically by the clock.
+        """
+        super().stop(clock)
 
     async def start_network(self):
-        self._stop_network()
+        """
+        This function is required by NetworkIterator base class and is called automatically.
+        It starts tracking order book, polling trading rules,
+        updating statuses and tracking user data.
+        """
+        await self.stop_network()
         self._order_book_tracker.start()
         self._trading_rules_polling_task = safe_ensure_future(self._trading_rules_polling_loop())
 
@@ -211,7 +208,7 @@ async def start_network(self):
             self._user_stream_event_listener_task = safe_ensure_future(self._user_stream_event_listener())
             await self._update_balances()
 
-    def _stop_network(self):
+    async def stop_network(self):
         self._order_book_tracker.stop()
         if self._status_polling_task is not None:
             self._status_polling_task.cancel()
@@ -226,35 +223,26 @@ def _stop_network(self):
             self._user_stream_event_listener_task.cancel()
             self._user_stream_event_listener_task = None
 
-    async def stop_network(self):
-        self._stop_network()
-
     async def check_network(self) -> NetworkStatus:
         try:
-            await self._api_request(method="GET", path_url=MEXC_PING_URL)
+            resp = await self._api_request(method="GET", path_url=CONSTANTS.MEXC_PING_URL)
+            if 'code' not in resp or resp['code'] != 200:
+                raise Exception()
         except asyncio.CancelledError:
             raise
-        except Exception as ex:
+        except Exception:
             return NetworkStatus.NOT_CONNECTED
         return NetworkStatus.CONNECTED
 
-    cdef c_tick(self, double timestamp):
+    def tick(self, timestamp: float):
         """
         Is called automatically by the clock for each clock's tick (1 second by default).
         It checks if status polling task is due for execution.
         """
-        cdef:
-            double now = time.time()
-            # double poll_interval = (self.SHORT_POLL_INTERVAL
-            #                         if now - self.user_stream_tracker.last_recv_time > 60.0
-            #                         else self.LONG_POLL_INTERVAL
-            #                         )
-            double poll_interval = self.MORE_SHORT_POLL_INTERVAL
-
-            int64_t last_tick = <int64_t> (self._last_timestamp / poll_interval)
-            int64_t current_tick = <int64_t> (timestamp / poll_interval)
-        ExchangeBase.c_tick(self, timestamp)
-        self._tx_tracker.c_tick(timestamp)
+        # now = time.time()
+        poll_interval = self.MORE_SHORT_POLL_INTERVAL
+        last_tick = int(self._last_timestamp / poll_interval)
+        current_tick = int(timestamp / poll_interval)
         if current_tick > last_tick:
             if not self._poll_notifier.is_set():
                 self._poll_notifier.set()
@@ -270,39 +258,35 @@ async def _api_request(self,
                            path_url: str,
                            params: Optional[Dict[str, Any]] = {},
                            data={},
-                           is_auth_required: bool = False) -> Dict[str, Any]:
+                           is_auth_required: bool = False,
+                           limit_id: Optional[str] = None) -> Dict[str, Any]:
 
         headers = {"Content-Type": "application/json"}
         client = await self._http_client()
         text_data = ujson.dumps(data) if data else None
-
+        limit_id = limit_id or path_url
         path_url = self._mexc_auth.add_auth_to_params(method, path_url, params, is_auth_required)
-        url = urljoin(MEXC_BASE_URL, path_url)
-        response_core = client.request(
-            method=method.upper(),
-            url=url,
-            headers=headers,
-            # params=params if params else None, #mexc`s params  is already in the url
-            data=text_data,
-        )
+        url = urljoin(CONSTANTS.MEXC_BASE_URL, path_url)
+        async with self._throttler.execute_task(limit_id):
+            response_core = await client.request(
+                method=method.upper(),
+                url=url,
+                headers=headers,
+                # params=params if params else None, #mexc`s params  is already in the url
+                data=text_data,
+            )
 
-        async with response_core as response:
-            if response.status != 200:
-                raise IOError(f"Error request from {url}. Response: {await response.json()}.")
-            try:
-                parsed_response = await response.json()
-                return parsed_response
-            except Exception as ex:
-                raise IOError(f"Error parsing data from {url}." + repr(ex))
+        # async with response_core as response:
+        if response_core.status != 200:
+            raise IOError(f"Error request from {url}. Response: {await response_core.json()}.")
+        try:
+            parsed_response = await response_core.json()
+            return parsed_response
+        except Exception as ex:
+            raise IOError(f"Error parsing data from {url}." + repr(ex))
 
     async def _update_balances(self):
-        cdef:
-            str path_url = MEXC_BALANCE_URL
-            dict balances = {}
-            dict new_available_balances = {}
-            dict new_balances = {}
-            str asset_name
-            object balance
+        path_url = CONSTANTS.MEXC_BALANCE_URL
         msg = await self._api_request("GET", path_url=path_url, is_auth_required=True)
         if msg['code'] == 200:
             balances = msg['data']
@@ -318,45 +302,29 @@ async def _update_balances(self):
             self._account_balances[k] = Decimal(balance['frozen']) + Decimal(balance['available'])
             self._account_available_balances[k] = Decimal(balance['available'])
 
-    cdef object c_get_fee(self,
-                          str base_currency,
-                          str quote_currency,
-                          object order_type,
-                          object order_side,
-                          object amount,
-                          object price):
-
-        is_maker = order_type is OrderType.LIMIT_MAKER
-        return estimate_fee("mexc", is_maker)
-
     async def _update_trading_rules(self):
-        cdef:
-            int64_t last_tick = 0
-            int64_t current_tick = 0
         try:
-            last_tick = <int64_t> (self._last_timestamp / 60.0)
-            current_tick = <int64_t> (self._current_timestamp / 60.0)
+            last_tick = int(self._last_timestamp / 60.0)
+            current_tick = int(self.current_timestamp / 60.0)
             if current_tick > last_tick or len(self._trading_rules) < 1:
-                exchange_info = await self._api_request("GET", path_url=MEXC_SYMBOL_URL)
+                exchange_info = await self._api_request("GET", path_url=CONSTANTS.MEXC_SYMBOL_URL)
                 trading_rules_list = self._format_trading_rules(exchange_info['data'])
                 self._trading_rules.clear()
                 for trading_rule in trading_rules_list:
                     self._trading_rules[trading_rule.trading_pair] = trading_rule
         except Exception as ex:
-            self.logger().error(f"Error _update_trading_rules:" + str(ex), exc_info=True)
+            self.logger().error("Error _update_trading_rules:" + str(ex), exc_info=True)
 
     def _format_trading_rules(self, raw_trading_pair_info: List[Dict[str, Any]]) -> List[TradingRule]:
-        cdef:
-            list trading_rules = []
-
+        trading_rules = []
         for info in raw_trading_pair_info:
             try:
                 trading_rules.append(
                     TradingRule(trading_pair=convert_from_exchange_trading_pair(info['symbol']),
                                 # min_order_size=Decimal(info["min_amount"]),
                                 # max_order_size=Decimal(info["max_amount"]),
-                                min_price_increment=Decimal(mexc_utils.num_to_increment(info["price_scale"])),
-                                min_base_amount_increment=Decimal(mexc_utils.num_to_increment(info["quantity_scale"])),
+                                min_price_increment=Decimal(num_to_increment(info["price_scale"])),
+                                min_base_amount_increment=Decimal(num_to_increment(info["quantity_scale"])),
                                 # min_quote_amount_increment=Decimal(info["1e-{info['value-precision']}"]),
                                 # min_notional_size=Decimal(info["min-order-value"])
                                 min_notional_size=Decimal(info["min_amount"]),
@@ -371,7 +339,7 @@ def _format_trading_rules(self, raw_trading_pair_info: List[Dict[str, Any]]) ->
     async def get_order_status(self, exchangge_order_id: str, trading_pair: str) -> Dict[str, Any]:
         params = {"order_ids": exchangge_order_id}
         msg = await self._api_request("GET",
-                                      path_url=MEXC_ORDER_DETAILS_URL,
+                                      path_url=CONSTANTS.MEXC_ORDER_DETAILS_URL,
                                       params=params,
                                       is_auth_required=True)
 
@@ -379,9 +347,8 @@ async def get_order_status(self, exchangge_order_id: str, trading_pair: str) ->
             return msg['data'][0]
 
     async def _update_order_status(self):
-        cdef:
-            int64_t last_tick = <int64_t> (self._last_poll_timestamp / self.UPDATE_ORDERS_INTERVAL)
-            int64_t current_tick = <int64_t> (self._current_timestamp / self.UPDATE_ORDERS_INTERVAL)
+        last_tick = int(self._last_poll_timestamp / self.UPDATE_ORDERS_INTERVAL)
+        current_tick = int(self.current_timestamp / self.UPDATE_ORDERS_INTERVAL)
         if current_tick > last_tick and len(self._in_flight_orders) > 0:
             tracked_orders = list(self._in_flight_orders.values())
             for tracked_order in tracked_orders:
@@ -391,13 +358,13 @@ async def _update_order_status(self):
                         order_update = await self.get_order_status(exchange_order_id, tracked_order.trading_pair)
                     except MexcAPIError as ex:
                         err_code = ex.error_payload.get("error").get('err-code')
-                        self.c_stop_tracking_order(tracked_order.client_order_id)
+                        self.stop_tracking_order(tracked_order.client_order_id)
                         self.logger().info(f"The limit order {tracked_order.client_order_id} "
                                            f"has failed according to order status API. - {err_code}")
-                        self.c_trigger_event(
+                        self.trigger_event(
                             self.MARKET_ORDER_FAILURE_EVENT_TAG,
                             MarketOrderFailureEvent(
-                                self._current_timestamp,
+                                self.current_timestamp,
                                 tracked_order.client_order_id,
                                 tracked_order.order_type
                             )
@@ -424,14 +391,14 @@ async def _update_order_status(self):
                         tracked_order.executed_amount_quote = Decimal(order_update['deal_amount'])
 
                         order_filled_event = OrderFilledEvent(
-                            self._current_timestamp,
+                            self.current_timestamp,
                             tracked_order.client_order_id,
                             tracked_order.trading_pair,
                             tracked_order.trade_type,
                             tracked_order.order_type,
                             execute_price,
                             execute_amount_diff,
-                            self.c_get_fee(
+                            self.get_fee(
                                 tracked_order.base_asset,
                                 tracked_order.quote_asset,
                                 tracked_order.order_type,
@@ -443,50 +410,50 @@ async def _update_order_status(self):
                         )
                         self.logger().info(f"Filled {execute_amount_diff} out of {tracked_order.amount} of the "
                                            f"order {tracked_order.client_order_id}.")
-                        self.c_trigger_event(self.MARKET_ORDER_FILLED_EVENT_TAG, order_filled_event)
+                        self.trigger_event(self.MARKET_ORDER_FILLED_EVENT_TAG, order_filled_event)
                     if order_status == "FILLED":
-                        client = await self._http_client()
-                        fee_paid = await self.get_deal_detail_fee(client, tracked_order.exchange_order_id)
+                        fee_paid, fee_currency = await self.get_deal_detail_fee(tracked_order.exchange_order_id)
                         tracked_order.fee_paid = fee_paid
+                        tracked_order.fee_asset = fee_currency
                         tracked_order.last_state = order_status
-                        self.c_stop_tracking_order(tracked_order.client_order_id)
+                        self.stop_tracking_order(tracked_order.client_order_id)
                         if tracked_order.trade_type is TradeType.BUY:
                             self.logger().info(
                                 f"The BUY {tracked_order.order_type} order {tracked_order.client_order_id} has completed "
                                 f"according to order delta restful API.")
-                            self.c_trigger_event(self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG,
-                                                 BuyOrderCompletedEvent(self._current_timestamp,
-                                                                        tracked_order.client_order_id,
-                                                                        tracked_order.base_asset,
-                                                                        tracked_order.quote_asset,
-                                                                        tracked_order.fee_asset or tracked_order.quote_asset,
-                                                                        tracked_order.executed_amount_base,
-                                                                        tracked_order.executed_amount_quote,
-                                                                        tracked_order.fee_paid,
-                                                                        tracked_order.order_type))
+                            self.trigger_event(self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG,
+                                               BuyOrderCompletedEvent(self.current_timestamp,
+                                                                      tracked_order.client_order_id,
+                                                                      tracked_order.base_asset,
+                                                                      tracked_order.quote_asset,
+                                                                      tracked_order.fee_asset or tracked_order.quote_asset,
+                                                                      tracked_order.executed_amount_base,
+                                                                      tracked_order.executed_amount_quote,
+                                                                      tracked_order.fee_paid,
+                                                                      tracked_order.order_type))
                         elif tracked_order.trade_type is TradeType.SELL:
                             self.logger().info(
                                 f"The SELL {tracked_order.order_type} order {tracked_order.client_order_id} has completed "
                                 f"according to order delta restful API.")
-                            self.c_trigger_event(self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG,
-                                                 SellOrderCompletedEvent(self._current_timestamp,
-                                                                         tracked_order.client_order_id,
-                                                                         tracked_order.base_asset,
-                                                                         tracked_order.quote_asset,
-                                                                         tracked_order.fee_asset or tracked_order.quote_asset,
-                                                                         tracked_order.executed_amount_base,
-                                                                         tracked_order.executed_amount_quote,
-                                                                         tracked_order.fee_paid,
-                                                                         tracked_order.order_type))
+                            self.trigger_event(self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG,
+                                               SellOrderCompletedEvent(self.current_timestamp,
+                                                                       tracked_order.client_order_id,
+                                                                       tracked_order.base_asset,
+                                                                       tracked_order.quote_asset,
+                                                                       tracked_order.fee_asset or tracked_order.quote_asset,
+                                                                       tracked_order.executed_amount_base,
+                                                                       tracked_order.executed_amount_quote,
+                                                                       tracked_order.fee_paid,
+                                                                       tracked_order.order_type))
                         continue
                     if order_status == "CANCELED" or order_status == "PARTIALLY_CANCELED":
                         tracked_order.last_state = order_status
-                        self.c_stop_tracking_order(tracked_order.client_order_id)
+                        self.stop_tracking_order(tracked_order.client_order_id)
                         self.logger().info(f"Order {tracked_order.client_order_id} has been cancelled "
                                            f"according to order delta restful API.")
-                        self.c_trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG,
-                                             OrderCancelledEvent(self._current_timestamp,
-                                                                 tracked_order.client_order_id))
+                        self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG,
+                                           OrderCancelledEvent(self.current_timestamp,
+                                                               tracked_order.client_order_id))
                 except Exception as ex:
                     self.logger().error("_update_order_status error ..." + repr(ex), exc_info=True)
 
@@ -499,7 +466,7 @@ async def _status_polling_loop(self):
                     self._update_balances(),
                     self._update_order_status(),
                 )
-                self._last_poll_timestamp = self._current_timestamp
+                self._last_poll_timestamp = self.current_timestamp
             except asyncio.CancelledError:
                 raise
             except Exception as ex:
@@ -513,7 +480,7 @@ async def _trading_rules_polling_loop(self):
         while True:
             try:
                 await self._update_trading_rules()
-                await asyncio.sleep(60 * 5)
+                await asyncio.sleep(60)
             except asyncio.CancelledError:
                 raise
             except Exception as ex:
@@ -523,7 +490,7 @@ async def _trading_rules_polling_loop(self):
                                                       "Check network connection.")
                 await asyncio.sleep(0.5)
 
-    async def _iter_user_stream_queue(self) -> AsyncIterable[Dict[str, Any]]:
+    async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, Any]]:
         while True:
             try:
                 yield await self._user_stream_tracker.user_stream.get()
@@ -534,106 +501,105 @@ async def _iter_user_stream_queue(self) -> AsyncIterable[Dict[str, Any]]:
                 await asyncio.sleep(1.0)
 
     async def _user_stream_event_listener(self):
-        async for stream_message in self._iter_user_stream_queue():
+        async for stream_message in self._iter_user_event_queue():
+            # self.logger().info(f"stream_message:{stream_message}")
             try:
-
-                if 'channel' in stream_message.keys() and stream_message['channel'] == 'push.personal.account':  # reserved,not use
+                if 'channel' in stream_message.keys() and stream_message['channel'] == 'push.personal.account':
                     continue
-                elif 'channel' in stream_message.keys() and stream_message['channel'] == 'push.personal.order':  # order status
-                    client_order_id = stream_message["data"]["clientOrderId"]
-                    trading_pair = convert_from_exchange_trading_pair(stream_message["symbol"])
-                    order_status = ws_order_status_convert_to_str(stream_message["data"]["status"])  # 1:NEW,2:FILLED,3:PARTIALLY_FILLED,4:CANCELED,5:PARTIALLY_CANCELED
-
-                    tracked_order = self._in_flight_orders.get(client_order_id, None)
-
-                    if tracked_order is None:
-                        continue
-
-                    # Update balance in time
-                    await self._update_balances()
-
-                    if order_status in {"FILLED", "PARTIALLY_FILLED"}:
-                        new_execute_amount_diff = s_decimal_0
-                        executed_amount = Decimal(str(stream_message["data"]['quantity'])) - Decimal(
-                            str(stream_message["data"]['remainQuantity']))
-                        execute_price = Decimal(str(stream_message["data"]['price']))
-                        execute_amount_diff = executed_amount - tracked_order.executed_amount_base
-                        if execute_amount_diff > s_decimal_0:
-                            tracked_order.executed_amount_base = executed_amount
-                            tracked_order.executed_amount_quote = Decimal(str(stream_message["data"]['amount'])) - Decimal(
-                                str(stream_message["data"]['remainAmount']))
-
-                            current_fee = self.get_fee(tracked_order.base_asset,
-                                                       tracked_order.quote_asset,
-                                                       tracked_order.order_type,
-                                                       tracked_order.trade_type,
-                                                       execute_amount_diff,
-                                                       execute_price)
-
-                            self.logger().info(f"Filled {execute_amount_diff} out of {tracked_order.amount} of ")
-                            self.c_trigger_event(self.MARKET_ORDER_FILLED_EVENT_TAG,
-                                                 OrderFilledEvent(self._current_timestamp,
-                                                                  tracked_order.client_order_id,
-                                                                  tracked_order.trading_pair,
-                                                                  tracked_order.trade_type,
-                                                                  tracked_order.order_type,
-                                                                  execute_price,
-                                                                  execute_amount_diff,
-                                                                  current_fee,
-                                                                  exchange_trade_id=tracked_order.exchange_order_id))
-                    if order_status == "FILLED":
-                        client = await self._http_client()
-                        fee_paid = await self.get_deal_detail_fee(client, tracked_order.exchange_order_id)
-                        tracked_order.fee_paid = fee_paid
-                        tracked_order.last_state = order_status
-                        if tracked_order.trade_type is TradeType.BUY:
-                            self.logger().info(
-                                f"The BUY {tracked_order.order_type} order {tracked_order.client_order_id} has completed "
-                                f"according to order delta websocket API.")
-                            self.c_trigger_event(self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG,
-                                                 BuyOrderCompletedEvent(self._current_timestamp,
-                                                                        tracked_order.client_order_id,
-                                                                        tracked_order.base_asset,
-                                                                        tracked_order.quote_asset,
-                                                                        tracked_order.fee_asset or tracked_order.quote_asset,
-                                                                        tracked_order.executed_amount_base,
-                                                                        tracked_order.executed_amount_quote,
-                                                                        tracked_order.fee_paid,
-                                                                        tracked_order.order_type))
-                        elif tracked_order.trade_type is TradeType.SELL:
-                            self.logger().info(
-                                f"The SELL {tracked_order.order_type} order {tracked_order.client_order_id} has completed "
-                                f"according to order delta websocket API.")
-                            self.c_trigger_event(self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG,
-                                                 SellOrderCompletedEvent(self._current_timestamp,
-                                                                         tracked_order.client_order_id,
-                                                                         tracked_order.base_asset,
-                                                                         tracked_order.quote_asset,
-                                                                         tracked_order.fee_asset or tracked_order.quote_asset,
-                                                                         tracked_order.executed_amount_base,
-                                                                         tracked_order.executed_amount_quote,
-                                                                         tracked_order.fee_paid,
-                                                                         tracked_order.order_type))
-                        self.c_stop_tracking_order(tracked_order.client_order_id)
-                        continue
-
-                    if order_status == "CANCELED" or order_status == "PARTIALLY_CANCELED":
-                        tracked_order.last_state = order_status
-                        self.logger().info(f"Order {tracked_order.client_order_id} has been cancelled "
-                                           f"according to order delta websocket API.")
-                        self.c_trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG,
-                                             OrderCancelledEvent(self._current_timestamp,
-                                                                 tracked_order.client_order_id))
-                        self.c_stop_tracking_order(tracked_order.client_order_id)
-
+                elif 'channel' in stream_message.keys() and stream_message['channel'] == 'push.personal.order':
+                    await self._process_order_message(stream_message)
                 else:
-                    continue
+                    self.logger().debug(f"Unknown event received from the connector ({stream_message})")
             except asyncio.CancelledError:
                 raise
             except Exception as e:
                 self.logger().error(f"Unexpected error in user stream listener lopp. {e}", exc_info=True)
                 await asyncio.sleep(5.0)
 
+    async def _process_order_message(self, stream_message: Dict[str, Any]):
+        client_order_id = stream_message["data"]["clientOrderId"]
+        # trading_pair = convert_from_exchange_trading_pair(stream_message["symbol"])
+        # 1:NEW,2:FILLED,3:PARTIALLY_FILLED,4:CANCELED,5:PARTIALLY_CANCELED
+        order_status = ws_order_status_convert_to_str(stream_message["data"]["status"])
+        tracked_order = self._in_flight_orders.get(client_order_id, None)
+        if tracked_order is None:
+            return
+        # Update balance in time
+        await self._update_balances()
+
+        if order_status in {"FILLED", "PARTIALLY_FILLED"}:
+            executed_amount = Decimal(str(stream_message["data"]['quantity'])) - Decimal(
+                str(stream_message["data"]['remainQuantity']))
+            execute_price = Decimal(str(stream_message["data"]['price']))
+            execute_amount_diff = executed_amount - tracked_order.executed_amount_base
+            if execute_amount_diff > s_decimal_0:
+                tracked_order.executed_amount_base = executed_amount
+                tracked_order.executed_amount_quote = Decimal(
+                    str(stream_message["data"]['amount'])) - Decimal(
+                    str(stream_message["data"]['remainAmount']))
+
+                current_fee = self.get_fee(tracked_order.base_asset,
+                                           tracked_order.quote_asset,
+                                           tracked_order.order_type,
+                                           tracked_order.trade_type,
+                                           execute_amount_diff,
+                                           execute_price)
+                self.logger().info(f"Filled {execute_amount_diff} out of {tracked_order.amount} of ")
+                self.trigger_event(self.MARKET_ORDER_FILLED_EVENT_TAG,
+                                   OrderFilledEvent(self.current_timestamp,
+                                                    tracked_order.client_order_id,
+                                                    tracked_order.trading_pair,
+                                                    tracked_order.trade_type,
+                                                    tracked_order.order_type,
+                                                    execute_price,
+                                                    execute_amount_diff,
+                                                    current_fee,
+                                                    exchange_trade_id=tracked_order.exchange_order_id))
+        if order_status == "FILLED":
+            fee_paid, fee_currency = await self.get_deal_detail_fee(tracked_order.exchange_order_id)
+            tracked_order.fee_paid = fee_paid
+            tracked_order.fee_asset = fee_currency
+            tracked_order.last_state = order_status
+            if tracked_order.trade_type is TradeType.BUY:
+                self.logger().info(
+                    f"The BUY {tracked_order.order_type} order {tracked_order.client_order_id} has completed "
+                    f"according to order delta websocket API.")
+                self.trigger_event(self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG,
+                                   BuyOrderCompletedEvent(self.current_timestamp,
+                                                          tracked_order.client_order_id,
+                                                          tracked_order.base_asset,
+                                                          tracked_order.quote_asset,
+                                                          tracked_order.fee_asset or tracked_order.quote_asset,
+                                                          tracked_order.executed_amount_base,
+                                                          tracked_order.executed_amount_quote,
+                                                          tracked_order.fee_paid,
+                                                          tracked_order.order_type))
+            elif tracked_order.trade_type is TradeType.SELL:
+                self.logger().info(
+                    f"The SELL {tracked_order.order_type} order {tracked_order.client_order_id} has completed "
+                    f"according to order delta websocket API.")
+                self.trigger_event(self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG,
+                                   SellOrderCompletedEvent(self.current_timestamp,
+                                                           tracked_order.client_order_id,
+                                                           tracked_order.base_asset,
+                                                           tracked_order.quote_asset,
+                                                           tracked_order.fee_asset or tracked_order.quote_asset,
+                                                           tracked_order.executed_amount_base,
+                                                           tracked_order.executed_amount_quote,
+                                                           tracked_order.fee_paid,
+                                                           tracked_order.order_type))
+            self.stop_tracking_order(tracked_order.client_order_id)
+            return
+
+        if order_status == "CANCELED" or order_status == "PARTIALLY_CANCELED":
+            tracked_order.last_state = order_status
+            self.logger().info(f"Order {tracked_order.client_order_id} has been cancelled "
+                               f"according to order delta websocket API.")
+            self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG,
+                               OrderCancelledEvent(self.current_timestamp,
+                                                   tracked_order.client_order_id))
+            self.stop_tracking_order(tracked_order.client_order_id)
+
     @property
     def status_dict(self) -> Dict[str, bool]:
         return {
@@ -673,7 +639,7 @@ async def place_order(self,
 
         exchange_order_id = await self._api_request(
             "POST",
-            path_url=MEXC_PLACE_ORDER,
+            path_url=CONSTANTS.MEXC_PLACE_ORDER,
             params={},
             data=data,
             is_auth_required=True
@@ -687,30 +653,25 @@ async def execute_buy(self,
                           amount: Decimal,
                           order_type: OrderType,
                           price: Optional[Decimal] = s_decimal_0):
-        cdef:
-            TradingRule trading_rule = self._trading_rules[trading_pair]
-            object quote_amount
-            object decimal_amount
-            object decimal_price
-            str exchange_order_id
-            object tracked_order
+
+        trading_rule = self._trading_rules[trading_pair]
 
         if not order_type.is_limit_type():
-            self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
-                                 MarketOrderFailureEvent(self._current_timestamp, order_id, order_type))
+            self.trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
+                               MarketOrderFailureEvent(self.current_timestamp, order_id, order_type))
             raise Exception(f"Unsupported order type: {order_type}")
 
-        decimal_price = self.c_quantize_order_price(trading_pair, price)
-        decimal_amount = self.c_quantize_order_amount(trading_pair, amount, decimal_price)
+        decimal_price = self.quantize_order_price(trading_pair, price)
+        decimal_amount = self.quantize_order_amount(trading_pair, amount, decimal_price)
         if decimal_price * decimal_amount < trading_rule.min_notional_size:
-            self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
-                                 MarketOrderFailureEvent(self._current_timestamp, order_id, order_type))
+            self.trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
+                               MarketOrderFailureEvent(self.current_timestamp, order_id, order_type))
             raise ValueError(f"Buy order amount {decimal_amount} is lower than the notional size ")
         try:
             exchange_order_id = await self.place_order(order_id, trading_pair, decimal_amount, True, order_type,
                                                        decimal_price)
-            self.c_start_tracking_order(
-                client_order_id=order_id,
+            self.start_tracking_order(
+                order_id=order_id,
                 exchange_order_id=exchange_order_id,
                 trading_pair=trading_pair,
                 order_type=order_type,
@@ -722,19 +683,19 @@ async def execute_buy(self,
             if tracked_order is not None:
                 self.logger().info(
                     f"Created {order_type.name.upper()} buy order {order_id} for {decimal_amount} {trading_pair}.")
-            self.c_trigger_event(self.MARKET_BUY_ORDER_CREATED_EVENT_TAG,
-                                 BuyOrderCreatedEvent(
-                                     self._current_timestamp,
-                                     order_type,
-                                     trading_pair,
-                                     decimal_amount,
-                                     decimal_price,
-                                     order_id
-                                 ))
+            self.trigger_event(self.MARKET_BUY_ORDER_CREATED_EVENT_TAG,
+                               BuyOrderCreatedEvent(
+                                   self.current_timestamp,
+                                   order_type,
+                                   trading_pair,
+                                   decimal_amount,
+                                   decimal_price,
+                                   order_id
+                               ))
         except asyncio.CancelledError:
             raise
         except Exception as ex:
-            self.c_stop_tracking_order(order_id)
+            self.stop_tracking_order(order_id)
             order_type_str = order_type.name.lower()
 
             self.logger().network(
@@ -745,18 +706,13 @@ async def execute_buy(self,
                 exc_info=True,
                 app_warning_msg="Failed to submit buy order to Mexc. Check API key and network connection."
             )
-            self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
-                                 MarketOrderFailureEvent(self._current_timestamp, order_id, order_type))
-
-    cdef str c_buy(self,
-                   str trading_pair,
-                   object amount,
-                   object order_type=OrderType.LIMIT,
-                   object price=s_decimal_0,
-                   dict kwargs={}):
-        cdef:
-            int64_t tracking_nonce = <int64_t> get_tracking_nonce()
-            str order_id = str(f"buy-{trading_pair}-{tracking_nonce}")
+            self.trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
+                               MarketOrderFailureEvent(self.current_timestamp, order_id, order_type))
+
+    def buy(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET,
+            price: Decimal = s_decimal_NaN, **kwargs) -> str:
+        tracking_nonce = int(get_tracking_nonce())
+        order_id = str(f"buy-{trading_pair}-{tracking_nonce}")
         safe_ensure_future(self.execute_buy(order_id, trading_pair, amount, order_type, price))
         return order_id
 
@@ -766,31 +722,27 @@ async def execute_sell(self,
                            amount: Decimal,
                            order_type: OrderType,
                            price: Optional[Decimal] = s_decimal_0):
-        cdef:
-            TradingRule trading_rule = self._trading_rules[trading_pair]
-            object decimal_amount
-            object decimal_price
-            str exchange_order_id
-            object tracked_order
+
+        trading_rule = self._trading_rules[trading_pair]
 
         if not order_type.is_limit_type():
-            self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
-                                 MarketOrderFailureEvent(self._current_timestamp, order_id, order_type))
+            self.trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
+                               MarketOrderFailureEvent(self.current_timestamp, order_id, order_type))
             raise Exception(f"Unsupported order type: {order_type}")
 
-        decimal_price = self.c_quantize_order_price(trading_pair, price)
-        decimal_amount = self.c_quantize_order_amount(trading_pair, amount, decimal_price)
+        decimal_price = self.quantize_order_price(trading_pair, price)
+        decimal_amount = self.quantize_order_amount(trading_pair, amount, decimal_price)
 
         if decimal_price * decimal_amount < trading_rule.min_notional_size:
-            self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
-                                 MarketOrderFailureEvent(self._current_timestamp, order_id, order_type))
+            self.trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
+                               MarketOrderFailureEvent(self.current_timestamp, order_id, order_type))
             raise ValueError(f"Sell order amount {decimal_amount} is lower than the notional size ")
 
         try:
             exchange_order_id = await self.place_order(order_id, trading_pair, decimal_amount, False, order_type,
                                                        decimal_price)
-            self.c_start_tracking_order(
-                client_order_id=order_id,
+            self.start_tracking_order(
+                order_id=order_id,
                 exchange_order_id=exchange_order_id,
                 trading_pair=trading_pair,
                 order_type=order_type,
@@ -802,19 +754,19 @@ async def execute_sell(self,
             if tracked_order is not None:
                 self.logger().info(
                     f"Created {order_type.name.upper()} sell order {order_id} for {decimal_amount} {trading_pair}.")
-            self.c_trigger_event(self.MARKET_SELL_ORDER_CREATED_EVENT_TAG,
-                                 SellOrderCreatedEvent(
-                                     self._current_timestamp,
-                                     order_type,
-                                     trading_pair,
-                                     decimal_amount,
-                                     decimal_price,
-                                     order_id
-                                 ))
+            self.trigger_event(self.MARKET_SELL_ORDER_CREATED_EVENT_TAG,
+                               SellOrderCreatedEvent(
+                                   self.current_timestamp,
+                                   order_type,
+                                   trading_pair,
+                                   decimal_amount,
+                                   decimal_price,
+                                   order_id
+                               ))
         except asyncio.CancelledError:
             raise
         except Exception as ex:
-            self.c_stop_tracking_order(order_id)
+            self.stop_tracking_order(order_id)
             order_type_str = order_type.name.lower()
             self.logger().network(
                 f"Error submitting sell {order_type_str} order to Mexc for "
@@ -822,20 +774,16 @@ async def execute_sell(self,
                 f"{decimal_price if order_type is OrderType.LIMIT else ''}."
                 f"{decimal_price}." + ",ex:" + repr(ex),
                 exc_info=True,
-                app_warning_msg=f"Failed to submit sell order to Mexc. Check API key and network connection."
+                app_warning_msg="Failed to submit sell order to Mexc. Check API key and network connection."
             )
-            self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
-                                 MarketOrderFailureEvent(self._current_timestamp, order_id, order_type))
-
-    cdef str c_sell(self,
-                    str trading_pair,
-                    object amount,
-                    object order_type=OrderType.LIMIT,
-                    object price=s_decimal_0,
-                    dict kwargs={}):
-        cdef:
-            int64_t tracking_nonce = <int64_t> get_tracking_nonce()
-            str order_id = str(f"sell-{trading_pair}-{tracking_nonce}")
+            self.trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
+                               MarketOrderFailureEvent(self.current_timestamp, order_id, order_type))
+
+    def sell(self, trading_pair: str, amount: Decimal, order_type: OrderType = OrderType.MARKET,
+             price: Decimal = s_decimal_NaN, **kwargs) -> str:
+
+        tracking_nonce = int(get_tracking_nonce())
+        order_id = str(f"sell-{trading_pair}-{tracking_nonce}")
 
         safe_ensure_future(self.execute_sell(order_id, trading_pair, amount, order_type, price))
         return order_id
@@ -850,7 +798,7 @@ async def execute_cancel(self, trading_pair: str, client_order_id: str):
             params = {
                 "client_order_ids": client_order_id,
             }
-            response = await self._api_request("DELETE", path_url=MEXC_ORDER_CANCEL, params=params,
+            response = await self._api_request("DELETE", path_url=CONSTANTS.MEXC_ORDER_CANCEL, params=params,
                                                is_auth_required=True)
 
             if not response['code'] == 200:
@@ -864,7 +812,7 @@ async def execute_cancel(self, trading_pair: str, client_order_id: str):
                                 f"Check API key and network connection."
             )
 
-    cdef c_cancel(self, str trading_pair, str order_id):
+    def cancel(self, trading_pair: str, order_id: str):
         safe_ensure_future(self.execute_cancel(trading_pair, order_id))
         return order_id
 
@@ -889,7 +837,7 @@ async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]:
             try:
                 cancel_all_results = await self._api_request(
                     "DELETE",
-                    path_url=MEXC_ORDER_CANCEL,
+                    path_url=CONSTANTS.MEXC_ORDER_CANCEL,
                     params=params,
                     is_auth_required=True
                 )
@@ -898,46 +846,38 @@ async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]:
                     for o in orders_by_trading_pair[trading_pair]:
                         if o.client_order_id == order_result_client_order_id:
                             result_bool = True if order_result_value == "invalid order state" or order_result_value == "success" else False
-                            cancellation_results.append(CancellationResult(o.exchange_order_id, result_bool))
+                            cancellation_results.append(CancellationResult(o.client_order_id, result_bool))
                             if result_bool:
-                                self.c_trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG,
-                                                     OrderCancelledEvent(self._current_timestamp,
-                                                                         order_id=o.client_order_id,
-                                                                         exchange_order_id=o.exchange_order_id))
-                                self.c_stop_tracking_order(o.client_order_id)
+                                self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG,
+                                                   OrderCancelledEvent(self.current_timestamp,
+                                                                       order_id=o.client_order_id,
+                                                                       exchange_order_id=o.exchange_order_id))
+                                self.stop_tracking_order(o.client_order_id)
 
             except Exception as ex:
 
                 self.logger().network(
                     f"Failed to cancel all orders: {cancel_order_ids}" + repr(ex),
                     exc_info=True,
-                    app_warning_msg=f"Failed to cancel all orders on Mexc. Check API key and network connection."
+                    app_warning_msg="Failed to cancel all orders on Mexc. Check API key and network connection."
                 )
-
         return cancellation_results
 
-    cdef OrderBook c_get_order_book(self, str trading_pair):
-        cdef:
-            dict order_books = self._order_book_tracker.order_books
-
-        if trading_pair not in order_books:
+    def get_order_book(self, trading_pair: str) -> OrderBook:
+        if trading_pair not in self._order_book_tracker.order_books:
             raise ValueError(f"No order book exists for '{trading_pair}'.")
-        return order_books.get(trading_pair)
-
-    cdef c_did_timeout_tx(self, str tracking_id):
-        self.c_trigger_event(self.MARKET_TRANSACTION_FAILURE_EVENT_TAG,
-                             MarketTransactionFailureEvent(self._current_timestamp, tracking_id))
-
-    cdef c_start_tracking_order(self,
-                                str client_order_id,
-                                str exchange_order_id,
-                                str trading_pair,
-                                object order_type,
-                                object trade_type,
-                                object price,
-                                object amount):
-        self._in_flight_orders[client_order_id] = MexcInFlightOrder(
-            client_order_id=client_order_id,
+        return self._order_book_tracker.order_books[trading_pair]
+
+    def start_tracking_order(self,
+                             order_id: str,
+                             exchange_order_id: Optional[str],
+                             trading_pair: str,
+                             trade_type: TradeType,
+                             price: Decimal,
+                             amount: Decimal,
+                             order_type: OrderType):
+        self._in_flight_orders[order_id] = MexcInFlightOrder(
+            client_order_id=order_id,
             exchange_order_id=exchange_order_id,
             trading_pair=trading_pair,
             order_type=order_type,
@@ -946,28 +886,32 @@ async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]:
             amount=amount
         )
 
-    cdef c_stop_tracking_order(self, str order_id):
+    def stop_tracking_order(self, order_id: str):
         if order_id in self._in_flight_orders:
             del self._in_flight_orders[order_id]
 
-    cdef object c_get_order_price_quantum(self, str trading_pair, object price):
-        cdef:
-            TradingRule trading_rule = self._trading_rules[trading_pair]
-
+    def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal:
+        """
+        Used by quantize_order_price() in _create_order()
+        Returns a price step, a minimum price increment for a given trading pair.
+        """
+        trading_rule = self._trading_rules[trading_pair]
         return trading_rule.min_price_increment
 
-    cdef object c_get_order_size_quantum(self, str trading_pair, object price):
-        cdef:
-            TradingRule trading_rule = self._trading_rules[trading_pair]
+    def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal:
+        """
+        Used by quantize_order_price() in _create_order()
+        Returns an order amount step, a minimum amount increment for a given trading pair.
+        """
+        trading_rule = self._trading_rules[trading_pair]
         return Decimal(trading_rule.min_base_amount_increment)
 
-    cdef object c_quantize_order_amount(self, str trading_pair, object amount, object price=s_decimal_0):
-        cdef:
-            TradingRule trading_rule = self._trading_rules[trading_pair]
+    def quantize_order_amount(self, trading_pair: str, amount: Decimal, price: Decimal = s_decimal_0) -> Decimal:
+
+        trading_rule = self._trading_rules[trading_pair]
 
-            object quantized_amount = ExchangeBase.c_quantize_order_amount(self, trading_pair, amount)
-            object current_price = self.c_get_price(trading_pair, False)
-            object notional_size
+        quantized_amount = ExchangeBase.quantize_order_amount(self, trading_pair, amount)
+        current_price = self.get_price(trading_pair, False)
 
         calc_price = current_price if price == s_decimal_0 else price
 
@@ -978,20 +922,6 @@ async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]:
 
         return quantized_amount
 
-    def get_price(self, trading_pair: str, is_buy: bool) -> Decimal:
-        return self.c_get_price(trading_pair, is_buy)
-
-    def buy(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET,
-            price: Decimal = s_decimal_NaN, **kwargs) -> str:
-        return self.c_buy(trading_pair, amount, order_type, price, kwargs)
-
-    def sell(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET,
-             price: Decimal = s_decimal_NaN, **kwargs) -> str:
-        return self.c_sell(trading_pair, amount, order_type, price, kwargs)
-
-    def cancel(self, trading_pair: str, client_order_id: str):
-        return self.c_cancel(trading_pair, client_order_id)
-
     def get_fee(self,
                 base_currency: str,
                 quote_currency: str,
@@ -999,22 +929,21 @@ def get_fee(self,
                 order_side: TradeType,
                 amount: Decimal,
                 price: Decimal = s_decimal_NaN) -> TradeFee:
-        return self.c_get_fee(base_currency, quote_currency, order_type, order_side, amount, price)
-
-    def get_order_book(self, trading_pair: str) -> OrderBook:
-        return self.c_get_order_book(trading_pair)
+        is_maker = order_type is OrderType.LIMIT_MAKER
+        return TradeFee(percent=self.estimate_fee_pct(is_maker))
 
-    async def get_deal_detail_fee(self, client: aiohttp.ClientSession, order_id: str) -> Dict[str, Any]:
+    async def get_deal_detail_fee(self, order_id: str) -> Dict[str, Any]:
         params = {
             'order_id': order_id,
         }
-        msg = await self._api_request("GET", path_url=MEXC_DEAL_DETAIL, params=params, is_auth_required=True)
-        balances: list = []
+        msg = await self._api_request("GET", path_url=CONSTANTS.MEXC_DEAL_DETAIL, params=params, is_auth_required=True)
         fee = s_decimal_0
+        fee_currency = None
         if msg['code'] == 200:
             balances = msg['data']
         else:
             raise Exception(msg)
         for order in balances:
             fee += Decimal(order['fee'])
-        return fee
+            fee_currency = order['fee_currency']
+        return fee, fee_currency
diff --git a/hummingbot/connector/exchange/mexc/mexc_in_flight_order.pxd b/hummingbot/connector/exchange/mexc/mexc_in_flight_order.pxd
deleted file mode 100644
index 8ad15798ef1..00000000000
--- a/hummingbot/connector/exchange/mexc/mexc_in_flight_order.pxd
+++ /dev/null
@@ -1,4 +0,0 @@
-from hummingbot.connector.in_flight_order_base cimport InFlightOrderBase
-
-cdef class MexcInFlightOrder(InFlightOrderBase):
-    pass
diff --git a/hummingbot/connector/exchange/mexc/mexc_in_flight_order.pyx b/hummingbot/connector/exchange/mexc/mexc_in_flight_order.py
similarity index 73%
rename from hummingbot/connector/exchange/mexc/mexc_in_flight_order.pyx
rename to hummingbot/connector/exchange/mexc/mexc_in_flight_order.py
index 80a4eb18637..8e8b32d0fe2 100644
--- a/hummingbot/connector/exchange/mexc/mexc_in_flight_order.pyx
+++ b/hummingbot/connector/exchange/mexc/mexc_in_flight_order.py
@@ -11,7 +11,8 @@
 
 from hummingbot.connector.in_flight_order_base import InFlightOrderBase
 
-cdef class MexcInFlightOrder(InFlightOrderBase):
+
+class MexcInFlightOrder(InFlightOrderBase):
     def __init__(self,
                  client_order_id: str,
                  exchange_order_id: str,
@@ -31,6 +32,7 @@ def __init__(self,
             amount,
             initial_state  # submitted, partial-filled, cancelling, filled, canceled, partial-canceled
         )
+        self.fee_asset = self.quote_asset
 
     @property
     def is_done(self) -> bool:
@@ -48,19 +50,21 @@ def is_failure(self) -> bool:
     def is_open(self) -> bool:
         return self.last_state in {"NEW", "PARTIALLY_FILLED"}
 
+    def mark_as_filled(self):
+        self.last_state = "FILLED"
+
     @classmethod
     def from_json(cls, data: Dict[str, Any]) -> InFlightOrderBase:
-        cdef:
-            MexcInFlightOrder retval = MexcInFlightOrder(
-                client_order_id=data["client_order_id"],
-                exchange_order_id=data["exchange_order_id"],
-                trading_pair=data["trading_pair"],
-                order_type=getattr(OrderType, data["order_type"]),
-                trade_type=getattr(TradeType, data["trade_type"]),
-                price=Decimal(data["price"]),
-                amount=Decimal(data["amount"]),
-                initial_state=data["last_state"]
-            )
+        retval = MexcInFlightOrder(
+            client_order_id=data["client_order_id"],
+            exchange_order_id=data["exchange_order_id"],
+            trading_pair=data["trading_pair"],
+            order_type=getattr(OrderType, data["order_type"]),
+            trade_type=getattr(TradeType, data["trade_type"]),
+            price=Decimal(data["price"]),
+            amount=Decimal(data["amount"]),
+            initial_state=data["last_state"]
+        )
         retval.executed_amount_base = Decimal(data["executed_amount_base"])
         retval.executed_amount_quote = Decimal(data["executed_amount_quote"])
         retval.fee_asset = data["fee_asset"]
diff --git a/hummingbot/connector/exchange/mexc/mexc_order_book.pxd b/hummingbot/connector/exchange/mexc/mexc_order_book.pxd
deleted file mode 100644
index 737efe645c6..00000000000
--- a/hummingbot/connector/exchange/mexc/mexc_order_book.pxd
+++ /dev/null
@@ -1,4 +0,0 @@
-from hummingbot.core.data_type.order_book cimport OrderBook
-
-cdef class MexcOrderBook(OrderBook):
-    pass
diff --git a/hummingbot/connector/exchange/mexc/mexc_order_book.pyx b/hummingbot/connector/exchange/mexc/mexc_order_book.py
similarity index 92%
rename from hummingbot/connector/exchange/mexc/mexc_order_book.pyx
rename to hummingbot/connector/exchange/mexc/mexc_order_book.py
index 22eea7f1613..d0837c5ecf8 100644
--- a/hummingbot/connector/exchange/mexc/mexc_order_book.pyx
+++ b/hummingbot/connector/exchange/mexc/mexc_order_book.py
@@ -12,15 +12,16 @@
 from hummingbot.core.data_type.order_book import OrderBook
 from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType
 
-_mexcob_logger = None
+_logger = None
 
-cdef class MexcOrderBook(OrderBook):
+
+class MexcOrderBook(OrderBook):
     @classmethod
     def logger(cls) -> HummingbotLogger:
-        global _mexcob_logger
-        if _mexcob_logger is None:
-            _mexcob_logger = logging.getLogger(__name__),
-        return _mexcob_logger
+        global _logger
+        if _logger is None:
+            _logger = logging.getLogger(__name__),
+        return _logger
 
     @classmethod
     def snapshot_message_from_exchange(cls,
diff --git a/hummingbot/connector/exchange/mexc/mexc_order_book_message.py b/hummingbot/connector/exchange/mexc/mexc_order_book_message.py
index f1ebdc3d1f8..f2f1caa2534 100644
--- a/hummingbot/connector/exchange/mexc/mexc_order_book_message.py
+++ b/hummingbot/connector/exchange/mexc/mexc_order_book_message.py
@@ -55,3 +55,6 @@ def bids(self) -> (List[OrderBookRow]):
             OrderBookRow(float(bid["price"]), float(bid["quantity"]), self.update_id)
             for bid in self.content.get("bids", [])
         ]
+
+    def __hash__(self) -> int:
+        return hash((self.type, self.timestamp))
diff --git a/hummingbot/connector/exchange/mexc/mexc_order_book_tracker.py b/hummingbot/connector/exchange/mexc/mexc_order_book_tracker.py
index 0724a6fdc5b..1b93720d19e 100644
--- a/hummingbot/connector/exchange/mexc/mexc_order_book_tracker.py
+++ b/hummingbot/connector/exchange/mexc/mexc_order_book_tracker.py
@@ -9,13 +9,17 @@
     Optional
 )
 
+import aiohttp
 from hummingbot.core.data_type.order_book import OrderBook
+
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
 from hummingbot.core.data_type.order_book_message import (
     OrderBookMessage,
     OrderBookMessageType
 )
 
 from hummingbot.core.data_type.order_book_tracker import OrderBookTracker
+from hummingbot.core.utils.async_utils import safe_ensure_future
 from hummingbot.logger import HummingbotLogger
 from hummingbot.connector.exchange.mexc.mexc_api_order_book_data_source import MexcAPIOrderBookDataSource
 
@@ -30,16 +34,28 @@ def logger(cls) -> HummingbotLogger:
         return cls._mexcobt_logger
 
     def __init__(self,
-                 trading_pairs: Optional[List[str]] = None):
-        super().__init__(MexcAPIOrderBookDataSource(trading_pairs), trading_pairs)
+                 trading_pairs: Optional[List[str]] = None,
+                 shared_client: Optional[aiohttp.ClientSession] = None,
+                 throttler: Optional[AsyncThrottler] = None,):
+        super().__init__(MexcAPIOrderBookDataSource(trading_pairs, shared_client=shared_client, throttler=throttler), trading_pairs)
         self._order_book_diff_stream: asyncio.Queue = asyncio.Queue()
         self._order_book_snapshot_stream: asyncio.Queue = asyncio.Queue()
         self._ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
+        self._order_book_stream_listener_task: Optional[asyncio.Task] = None
 
     @property
     def exchange_name(self) -> str:
         return "mexc"
 
+    def start(self):
+        super().start()
+        self._order_book_stream_listener_task = safe_ensure_future(self._data_source.listen_for_subscriptions())
+
+    def stop(self):
+        if self._order_book_stream_listener_task:
+            self._order_book_stream_listener_task.cancel()
+        super().stop()
+
     async def _order_book_diff_router(self):
         last_message_timestamp: float = time.time()
         messages_queued: int = 0
diff --git a/hummingbot/connector/exchange/mexc/mexc_user_stream_tracker.py b/hummingbot/connector/exchange/mexc/mexc_user_stream_tracker.py
index a891a17c9e6..5bdbc85c3d0 100644
--- a/hummingbot/connector/exchange/mexc/mexc_user_stream_tracker.py
+++ b/hummingbot/connector/exchange/mexc/mexc_user_stream_tracker.py
@@ -7,6 +7,10 @@
     Optional,
     List
 )
+
+import aiohttp
+
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
 from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource
 from hummingbot.logger import HummingbotLogger
 from hummingbot.core.data_type.user_stream_tracker import UserStreamTracker
@@ -29,20 +33,27 @@ def logger(cls) -> HummingbotLogger:
         return cls._mexcust_logger
 
     def __init__(self,
+                 throttler: AsyncThrottler,
                  mexc_auth: Optional[MexcAuth] = None,
                  trading_pairs: Optional[List[str]] = [],
+                 shared_client: Optional[aiohttp.ClientSession] = None
                  ):
         super().__init__()
+        self._shared_client = shared_client
         self._ev_loop: asyncio.events.AbstractEventLoop = asyncio.get_event_loop()
         self._data_source: Optional[UserStreamTrackerDataSource] = None
         self._user_stream_tracking_task: Optional[asyncio.Task] = None
         self._mexc_auth: MexcAuth = mexc_auth
         self._trading_pairs: List[str] = trading_pairs
+        self._throttler = throttler
 
     @property
     def data_source(self) -> UserStreamTrackerDataSource:
         if not self._data_source:
-            self._data_source = MexcAPIUserStreamDataSource(mexc_auth=self._mexc_auth, trading_pairs=self._trading_pairs)
+            self._data_source = MexcAPIUserStreamDataSource(throttler=self._throttler,
+                                                            mexc_auth=self._mexc_auth,
+                                                            trading_pairs=self._trading_pairs,
+                                                            shared_client=self._shared_client)
         return self._data_source
 
     @property
diff --git a/hummingbot/connector/exchange/mexc/mexc_websocket_adaptor.py b/hummingbot/connector/exchange/mexc/mexc_websocket_adaptor.py
new file mode 100644
index 00000000000..4a7c2e7e618
--- /dev/null
+++ b/hummingbot/connector/exchange/mexc/mexc_websocket_adaptor.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+import json
+
+import aiohttp
+import asyncio
+import logging
+
+import hummingbot.connector.exchange.mexc.mexc_constants as CONSTANTS
+import hummingbot.connector.exchange.mexc.mexc_utils as mexc_utils
+
+from typing import Dict, Optional, AsyncIterable, Any, List
+
+from hummingbot.connector.exchange.mexc.mexc_auth import MexcAuth
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
+from hummingbot.logger import HummingbotLogger
+
+
+class MexcWebSocketAdaptor:
+
+    DEAL_CHANNEL_ID = "push.deal"
+    DEPTH_CHANNEL_ID = "push.depth"
+    SUBSCRIPTION_LIST = set([DEAL_CHANNEL_ID, DEPTH_CHANNEL_ID])
+
+    _ID_FIELD_NAME = "id"
+
+    _logger: Optional[HummingbotLogger] = None
+
+    MESSAGE_TIMEOUT = 120.0
+    PING_TIMEOUT = 10.0
+
+    @classmethod
+    def logger(cls) -> HummingbotLogger:
+        if cls._logger is None:
+            cls._logger = logging.getLogger(__name__)
+        return cls._logger
+
+    def __init__(
+            self,
+            throttler: AsyncThrottler,
+            auth: Optional[MexcAuth] = None,
+            shared_client: Optional[aiohttp.ClientSession] = None,
+    ):
+
+        self._auth: Optional[MexcAuth] = auth
+        self._is_private = True if self._auth is not None else False
+        self._WS_URL = CONSTANTS.MEXC_WS_URL_PUBLIC
+        self._shared_client = shared_client
+        self._websocket: Optional[aiohttp.ClientWebSocketResponse] = None
+        self._throttler = throttler
+
+    def get_shared_client(self) -> aiohttp.ClientSession:
+        if not self._shared_client:
+            self._shared_client = aiohttp.ClientSession()
+        return self._shared_client
+
+    async def send_request(self, payload: Dict[str, Any]):
+        await self._websocket.send_json(payload)
+
+    async def send_request_str(self, payload: str):
+        await self._websocket.send_str(payload)
+
+    async def subscribe_to_order_book_streams(self, trading_pairs: List[str]):
+        try:
+            for trading_pair in trading_pairs:
+                trading_pair = mexc_utils.convert_to_exchange_trading_pair(trading_pair)
+                subscribe_deal_request: Dict[str, Any] = {
+                    "op": "sub.deal",
+                    "symbol": trading_pair,
+                }
+                async with self._throttler.execute_task(CONSTANTS.MEXC_WS_URL_PUBLIC):
+                    await self.send_request_str(json.dumps(subscribe_deal_request))
+                subscribe_depth_request: Dict[str, Any] = {
+                    "op": "sub.depth",
+                    "symbol": trading_pair,
+                }
+                async with self._throttler.execute_task(CONSTANTS.MEXC_WS_URL_PUBLIC):
+                    await self.send_request_str(json.dumps(subscribe_depth_request))
+
+        except asyncio.CancelledError:
+            raise
+        except Exception:
+            self.logger().error(
+                "Unexpected error occurred subscribing to order book trading and delta streams...", exc_info=True
+            )
+            raise
+
+    async def subscribe_to_user_streams(self):
+        pass
+
+    async def authenticate(self):
+        pass
+
+    async def connect(self):
+        try:
+            self._websocket = await self.get_shared_client().ws_connect(
+                url=self._WS_URL)
+
+        except Exception as e:
+            self.logger().error(f"Websocket error: '{str(e)}'", exc_info=True)
+            raise
+
+    # disconnect from exchange
+    async def disconnect(self):
+        if self._websocket is None:
+            return
+        await self._websocket.close()
+
+    async def iter_messages(self) -> AsyncIterable[Any]:
+        try:
+            while True:
+                try:
+                    msg = await asyncio.wait_for(self._websocket.receive(), timeout=self.MESSAGE_TIMEOUT)
+                    if msg.type == aiohttp.WSMsgType.CLOSED:
+                        raise ConnectionError
+                    yield json.loads(msg.data)
+                except asyncio.TimeoutError:
+                    pong_waiter = self._websocket.ping()
+                    self.logger().warning("WebSocket receive_json timeout ...")
+                    await asyncio.wait_for(pong_waiter, timeout=self.PING_TIMEOUT)
+        except ConnectionError:
+            return
diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py b/test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py
new file mode 100644
index 00000000000..6185f016cb1
--- /dev/null
+++ b/test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py
@@ -0,0 +1,216 @@
+import unittest
+import asyncio
+from collections import deque
+
+import ujson
+
+import hummingbot.connector.exchange.mexc.mexc_constants as CONSTANTS
+
+from unittest.mock import patch, AsyncMock
+from typing import (
+    Any,
+    Dict,
+)
+
+from hummingbot.connector.exchange.mexc.mexc_api_order_book_data_source import MexcAPIOrderBookDataSource
+
+from hummingbot.core.data_type.order_book import OrderBook
+
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
+from hummingbot.core.data_type.order_book_message import OrderBookMessageType
+from hummingbot.core.utils.async_utils import safe_ensure_future
+from test.hummingbot.connector.network_mocking_assistant import NetworkMockingAssistant
+
+
+class MexcAPIOrderBookDataSourceUnitTests(unittest.TestCase):
+    # logging.Level required to receive logs from the data source logger
+    level = 0
+
+    @classmethod
+    def setUpClass(cls) -> None:
+        super().setUpClass()
+
+        cls.ev_loop = asyncio.get_event_loop()
+        cls.base_asset = "BTC"
+        cls.quote_asset = "USDT"
+        cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}"
+        cls.instrument_id = 1
+
+    def setUp(self) -> None:
+        super().setUp()
+        self.log_records = []
+        self.listening_task = None
+
+        self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS)
+        self.data_source = MexcAPIOrderBookDataSource(throttler=self.throttler, trading_pairs=[self.trading_pair])
+        self.data_source.logger().setLevel(1)
+        self.data_source.logger().addHandler(self)
+
+        self.mocking_assistant = NetworkMockingAssistant()
+
+    def tearDown(self) -> None:
+        self.listening_task and self.listening_task.cancel()
+        super().tearDown()
+
+    def handle(self, record):
+        self.log_records.append(record)
+
+    def _raise_exception(self, exception_class):
+        raise exception_class
+
+    def _is_logged(self, log_level: str, message: str) -> bool:
+        return any(record.levelname == log_level and record.getMessage() == message
+                   for record in self.log_records)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_last_traded_prices(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        mock_response: Dict[Any] = {"code": 200, "data": [
+            {"symbol": "BTC_USDT", "volume": "1076.002782", "high": "59387.98", "low": "57009", "bid": "57920.98",
+             "ask": "57921.03", "open": "57735.92", "last": "57902.52", "time": 1637898900000,
+             "change_rate": "0.00288555"}]}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response)
+
+        results = self.ev_loop.run_until_complete(
+            asyncio.gather(self.data_source.get_last_traded_prices([self.trading_pair])))
+        results: Dict[str, Any] = results[0]
+
+        self.assertEqual(results[self.trading_pair], 57902.52)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_fetch_trading_pairs_with_error_status_in_response(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        mock_response = {}
+        self.mocking_assistant.add_http_response(mock_api, 100, mock_response)
+
+        result = self.ev_loop.run_until_complete(self.data_source.fetch_trading_pairs())
+        self.assertEqual(0, len(result))
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_order_book_data(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        mock_response = {"code": 200, "data": {"asks": [{"price": "57974.06", "quantity": "0.247421"}],
+                                               "bids": [{"price": "57974.01", "quantity": "0.201635"}],
+                                               "version": "562370278"}}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response)
+
+        results = self.ev_loop.run_until_complete(
+            asyncio.gather(self.data_source.get_snapshot(self.data_source._shared_client, self.trading_pair)))
+        result = results[0]
+
+        self.assertTrue("asks" in result)
+        self.assertGreaterEqual(len(result), 0)
+        self.assertEqual(mock_response.get("data"), result)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_order_book_data_raises_exception_when_response_has_error_code(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+
+        mock_response = {"Erroneous response"}
+        self.mocking_assistant.add_http_response(mock_api, 100, mock_response)
+
+        with self.assertRaises(IOError) as context:
+            self.ev_loop.run_until_complete(self.data_source.get_snapshot(self.data_source._shared_client,
+                                                                          self.trading_pair))
+
+        self.assertEqual(str(context.exception),
+                         f'Error fetching MEXC market snapshot for {self.trading_pair.replace("-", "_")}. '
+                         f'HTTP status is {100}.')
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_new_order_book(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+
+        mock_response = {"code": 200, "data": {"asks": [{"price": "57974.06", "quantity": "0.247421"}],
+                                               "bids": [{"price": "57974.01", "quantity": "0.201635"}],
+                                               "version": "562370278"}}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response)
+
+        results = self.ev_loop.run_until_complete(
+            asyncio.gather(self.data_source.get_new_order_book(self.trading_pair)))
+        result: OrderBook = results[0]
+
+        self.assertTrue(type(result) == OrderBook)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_listen_for_snapshots_cancelled_when_fetching_snapshot(self, mock_api):
+        mock_api.side_effect = asyncio.CancelledError
+
+        msg_queue: asyncio.Queue = asyncio.Queue()
+        with self.assertRaises(asyncio.CancelledError):
+            self.listening_task = self.ev_loop.create_task(
+                self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)
+            )
+            self.ev_loop.run_until_complete(self.listening_task)
+
+        self.assertEqual(msg_queue.qsize(), 0)
+
+    @patch("asyncio.sleep")
+    @patch("aiohttp.ClientSession.get")
+    def test_listen_for_snapshots_successful(self, mock_api, mock_sleep):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+
+        # the queue and the division by zero error are used just to synchronize the test
+        sync_queue = deque()
+        sync_queue.append(1)
+
+        mock_response = {"code": 200, "data": {"asks": [{"price": "57974.06", "quantity": "0.247421"}],
+                                               "bids": [{"price": "57974.01", "quantity": "0.201635"}],
+                                               "version": "562370278"}}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response)
+
+        mock_sleep.side_effect = lambda delay: 1 / 0 if len(sync_queue) == 0 else sync_queue.pop()
+
+        msg_queue: asyncio.Queue = asyncio.Queue()
+        with self.assertRaises(ZeroDivisionError):
+            self.listening_task = self.ev_loop.create_task(
+                self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue))
+            self.ev_loop.run_until_complete(self.listening_task)
+
+        self.assertEqual(msg_queue.qsize(), 1)
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_listen_for_subscriptions_cancelled_when_subscribing(self, mock_ws):
+        mock_ws.return_value = self.mocking_assistant.create_websocket_mock()
+        mock_ws.return_value.send_str.side_effect = asyncio.CancelledError()
+
+        self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, {'channel': 'push.personal.order'})
+
+        with self.assertRaises(asyncio.CancelledError):
+            self.listening_task = self.ev_loop.create_task(
+                self.data_source.listen_for_subscriptions()
+            )
+            self.ev_loop.run_until_complete(self.listening_task)
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_listen_for_order_book_diffs_cancelled_when_listening(self, mock_ws):
+        msg_queue: asyncio.Queue = asyncio.Queue()
+        mock_ws.return_value = self.mocking_assistant.create_websocket_mock()
+        data = {'symbol': 'MX_USDT',
+                'data': {'version': '44000093', 'bids': [{'p': '2.9311', 'q': '0.00', 'a': '0.00000000'}],
+                         'asks': [{'p': '2.9311', 'q': '22720.37', 'a': '66595.6765'}]},
+                'channel': 'push.depth'}
+        self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, ujson.dumps(data))
+        safe_ensure_future(self.data_source.listen_for_subscriptions())
+
+        self.listening_task = self.ev_loop.create_task(
+            self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue))
+
+        first_msg = self.ev_loop.run_until_complete(msg_queue.get())
+        self.assertTrue(first_msg.type == OrderBookMessageType.DIFF)
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_websocket_connection_creation_raises_cancel_exception(self, mock_ws):
+        mock_ws.side_effect = asyncio.CancelledError
+
+        with self.assertRaises(asyncio.CancelledError):
+            asyncio.get_event_loop().run_until_complete(self.data_source._create_websocket_connection())
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_websocket_connection_creation_raises_exception_after_loging(self, mock_ws):
+        mock_ws.side_effect = Exception
+
+        with self.assertRaises(Exception):
+            asyncio.get_event_loop().run_until_complete(self.data_source._create_websocket_connection())
+
+        self.assertTrue(self._is_logged("NETWORK", 'Unexpected error occured connecting to mexc WebSocket API. ()'))
diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/mexc/test_mexc_api_user_stream_data_source.py
new file mode 100644
index 00000000000..f5d6d8ed4f1
--- /dev/null
+++ b/test/hummingbot/connector/exchange/mexc/test_mexc_api_user_stream_data_source.py
@@ -0,0 +1,76 @@
+import asyncio
+from unittest import TestCase
+from unittest.mock import patch, AsyncMock
+
+import ujson
+
+from hummingbot.connector.exchange.mexc.mexc_api_user_stream_data_source import MexcAPIUserStreamDataSource
+from hummingbot.connector.exchange.mexc.mexc_auth import MexcAuth
+import hummingbot.connector.exchange.mexc.mexc_constants as CONSTANTS
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
+from test.hummingbot.connector.network_mocking_assistant import NetworkMockingAssistant
+
+
+class MexcAPIUserStreamDataSourceTests(TestCase):
+    # the level is required to receive logs from the data source loger
+    level = 0
+
+    def setUp(self) -> None:
+        super().setUp()
+        self.uid = '001'
+        self.api_key = 'testAPIKey'
+        self.secret = 'testSecret'
+        self.account_id = 528
+        self.username = 'hbot'
+        self.oms_id = 1
+        self.log_records = []
+        self.listening_task = None
+
+        throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS)
+        auth_assistant = MexcAuth(api_key=self.api_key,
+                                  secret_key=self.secret)
+        self.data_source = MexcAPIUserStreamDataSource(throttler, auth_assistant)
+        self.data_source.logger().setLevel(1)
+        self.data_source.logger().addHandler(self)
+
+        self.mocking_assistant = NetworkMockingAssistant()
+
+    def tearDown(self) -> None:
+        self.listening_task and self.listening_task.cancel()
+        super().tearDown()
+
+    def handle(self, record):
+        self.log_records.append(record)
+
+    def _is_logged(self, log_level: str, message: str) -> bool:
+        return any(record.levelname == log_level and record.getMessage() == message
+                   for record in self.log_records)
+
+    def _raise_exception(self, exception_class):
+        raise exception_class
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_listening_process_authenticates_and_subscribes_to_events(self, ws_connect_mock):
+        messages = asyncio.Queue()
+        ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock()
+
+        self.listening_task = asyncio.get_event_loop().create_task(
+            self.data_source.listen_for_user_stream(asyncio.get_event_loop(),
+                                                    messages))
+        # Add a dummy message for the websocket to read and include in the "messages" queue
+        self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value,
+                                                             ujson.dumps({'channel': 'push.personal.order'}))
+
+        first_received_message = asyncio.get_event_loop().run_until_complete(messages.get())
+        self.assertEqual({'channel': 'push.personal.order'}, first_received_message)
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_listening_process_canceled_when_cancel_exception_during_initialization(self, ws_connect_mock):
+        messages = asyncio.Queue()
+        ws_connect_mock.side_effect = asyncio.CancelledError
+
+        with self.assertRaises(asyncio.CancelledError):
+            self.listening_task = asyncio.get_event_loop().create_task(
+                self.data_source.listen_for_user_stream(asyncio.get_event_loop(),
+                                                        messages))
+            asyncio.get_event_loop().run_until_complete(self.listening_task)
diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_auth.py b/test/hummingbot/connector/exchange/mexc/test_mexc_auth.py
index 673277cb5b2..c36e3dbc13e 100644
--- a/test/hummingbot/connector/exchange/mexc/test_mexc_auth.py
+++ b/test/hummingbot/connector/exchange/mexc/test_mexc_auth.py
@@ -6,16 +6,19 @@
 
 
 class TestAuth(unittest.TestCase):
-    def setUp(self):
-        # cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
-        mexc_api_key = "MEXC_API_KEY_mock"
-        mexc_secret_key = "MEXC_SECRET_KEY_mock"
 
-        self.auth = MexcAuth(mexc_api_key, mexc_secret_key)
+    @property
+    def api_key(self):
+        return 'MEXC_API_KEY_mock'
+
+    @property
+    def secret_key(self):
+        return 'MEXC_SECRET_KEY_mock'
 
     @mock.patch('hummingbot.connector.exchange.mexc.mexc_utils.seconds', mock.MagicMock(return_value=1635249347))
     def test_auth_without_params(self):
+        self.auth = MexcAuth(self.api_key, self.secret_key)
         headers = self.auth.add_auth_to_params('GET', "/open/api/v2/market/coin/list",
-                                               {'api_key': "MEXC_API_KEY_mock"}, True)
+                                               {'api_key': self.api_key}, True)
         self.assertIn("api_key=MEXC_API_KEY_mock&req_time=1635249347"
                       "&sign=8dc59c2b7f0ad6da9e8844bb5478595a4f83126cb607524d767586437bae8d68", headers)
diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py b/test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py
new file mode 100644
index 00000000000..ace2acfa77d
--- /dev/null
+++ b/test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py
@@ -0,0 +1,1171 @@
+import asyncio
+import time
+import pandas as pd
+
+import ujson
+from decimal import Decimal
+from typing import Any, Dict, List
+from unittest import TestCase
+from unittest.mock import AsyncMock, PropertyMock, patch
+
+from hummingbot.connector.exchange.mexc.mexc_exchange import MexcExchange
+from hummingbot.connector.exchange.mexc.mexc_in_flight_order import (
+    MexcInFlightOrder
+)
+from hummingbot.connector.exchange.mexc.mexc_order_book import MexcOrderBook
+from hummingbot.connector.trading_rule import TradingRule
+from hummingbot.core.event.events import (
+    OrderCancelledEvent,
+    OrderType,
+    TradeType, SellOrderCompletedEvent,
+)
+from hummingbot.core.network_iterator import NetworkStatus
+from hummingbot.core.utils.async_utils import safe_ensure_future
+from test.hummingbot.connector.network_mocking_assistant import NetworkMockingAssistant
+
+
+class MexcExchangeTests(TestCase):
+    # the level is required to receive logs from the data source loger
+    level = 0
+
+    start_timestamp: float = pd.Timestamp("2021-01-01", tz="UTC").timestamp()
+
+    @classmethod
+    def setUpClass(cls) -> None:
+        super().setUpClass()
+        cls.base_asset = "MX"
+        cls.quote_asset = "USDT"
+        cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}"
+
+    def setUp(self) -> None:
+        super().setUp()
+
+        self.tracker_task = None
+        self.exchange_task = None
+        self.log_records = []
+        self.resume_test_event = asyncio.Event()
+        self._account_name = "hbot"
+
+        self.exchange = MexcExchange(mexc_api_key='testAPIKey',
+                                     mexc_secret_key='testSecret',
+                                     trading_pairs=[self.trading_pair])
+
+        self.exchange.logger().setLevel(1)
+        self.exchange.logger().addHandler(self)
+        self.exchange._account_id = 1
+
+        self.mocking_assistant = NetworkMockingAssistant()
+        self.mock_done_event = asyncio.Event()
+
+    def tearDown(self) -> None:
+        self.tracker_task and self.tracker_task.cancel()
+        self.exchange_task and self.exchange_task.cancel()
+        super().tearDown()
+
+    def handle(self, record):
+        self.log_records.append(record)
+
+    def _is_logged(self, log_level: str, message: str) -> bool:
+        return any(record.levelname == log_level and record.getMessage() == message
+                   for record in self.log_records)
+
+    def _create_exception_and_unlock_test_with_event(self, exception):
+        self.resume_test_event.set()
+        raise exception
+
+    def _mock_responses_done_callback(self, *_, **__):
+        self.mock_done_event.set()
+
+    def _simulate_reset_poll_notifier(self):
+        self.exchange._poll_notifier.clear()
+
+    def _simulate_ws_message_received(self, timestamp: float):
+        self.exchange._user_stream_tracker._data_source._last_recv_time = timestamp
+
+    def _simulate_trading_rules_initialized(self):
+        self.exchange._trading_rules = {
+            self.trading_pair: TradingRule(
+                trading_pair=self.trading_pair,
+                min_order_size=4,
+                min_price_increment=Decimal(str(0.0001)),
+                min_base_amount_increment=2,
+                min_notional_size=Decimal(str(5))
+            )
+        }
+
+    @property
+    def order_book_data(self):
+        _data = {"code": 200, "data": {
+            "asks": [{"price": "56454.0", "quantity": "0.799072"}, {"price": "56455.28", "quantity": "0.008663"}],
+            "bids": [{"price": "56451.0", "quantity": "0.008663"}, {"price": "56449.99", "quantity": "0.173078"}],
+            "version": "547878563"}}
+        return _data
+
+    def _simulate_create_order(self,
+                               trade_type: TradeType,
+                               order_id: str,
+                               trading_pair: str,
+                               amount: Decimal,
+                               price: Decimal = Decimal("0"),
+                               order_type: OrderType = OrderType.MARKET):
+        future = safe_ensure_future(
+            self.exchange.execute_buy(order_id, trading_pair, amount, order_type, price)
+        )
+        self.exchange.start_tracking_order(
+            order_id, None, self.trading_pair, TradeType.BUY, Decimal(10.0), Decimal(1.0), OrderType.LIMIT
+        )
+        return future
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_user_event_queue_error_is_logged(self, ws_connect_mock):
+        ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock()
+
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+
+        dummy_user_stream = AsyncMock()
+        dummy_user_stream.get.side_effect = lambda: self._create_exception_and_unlock_test_with_event(
+            Exception("Dummy test error"))
+        self.exchange._user_stream_tracker._user_stream = dummy_user_stream
+
+        # Add a dummy message for the websocket to read and include in the "messages" queue
+        self.mocking_assistant.add_websocket_text_message(ws_connect_mock,
+                                                          ujson.dumps({'channel': 'push.personal.order'}))
+        asyncio.get_event_loop().run_until_complete(self.resume_test_event.wait())
+        self.resume_test_event.clear()
+
+        try:
+            self.exchange_task.cancel()
+            asyncio.get_event_loop().run_until_complete(self.exchange_task)
+        except asyncio.CancelledError:
+            pass
+        except Exception:
+            pass
+
+        self.assertTrue(self._is_logged('ERROR', "Unknown error. Retrying after 1 second. Dummy test error"))
+
+    def test_user_event_queue_notifies_cancellations(self):
+        self.tracker_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+
+        dummy_user_stream = AsyncMock()
+        dummy_user_stream.get.side_effect = lambda: self._create_exception_and_unlock_test_with_event(
+            asyncio.CancelledError())
+        self.exchange._user_stream_tracker._user_stream = dummy_user_stream
+
+        # Yield processor for the exchange to process the new message
+        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.3))
+
+        with self.assertRaises(asyncio.CancelledError):
+            asyncio.get_event_loop().run_until_complete(self.tracker_task)
+
+    def test_exchange_logs_unknown_event_message(self):
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+        payload = {'channel': 'test'}
+        self.exchange._user_stream_tracker.user_stream.put_nowait(payload)
+
+        # Yield processor for the exchange to process the new message
+        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.3))
+
+        self.assertTrue(self._is_logged('DEBUG', f"Unknown event received from the connector ({payload})"))
+
+    @property
+    def balances_mock_data(self):
+        return {
+            "code": 200,
+            "data": {
+                "MX": {
+                    "frozen": "30.9863",
+                    "available": "450.0137"
+                }
+            }
+        }
+
+    @property
+    def user_stream_data(self):
+        return {
+            'symbol': 'MX_USDT',
+            'data': {
+                'price': 3.1504,
+                'quantity': 2,
+                'amount': 6.3008,
+                'remainAmount': 6.3008,
+                'remainQuantity': 2,
+                'remainQ': 2,
+                'id': '40728558ead64032a676e6f0a4afc4ca',
+                'status': 4,
+                'tradeType': 2,
+                'createTime': 1638156451000,
+                'symbolDisplay': 'MX_USDT',
+                'clientOrderId': 'sell-MX-USDT-1638156451005305'},
+            'channel': 'push.personal.order', 'symbol_display': 'MX_USDT'}
+
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_order_event_with_cancel_status_cancels_in_flight_order(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        self.mocking_assistant.add_http_response(mock_api, 200, self.balances_mock_data, self.balances_mock_data)
+
+        self.exchange.start_tracking_order(order_id="sell-MX-USDT-1638156451005305",
+                                           exchange_order_id="40728558ead64032a676e6f0a4afc4ca",
+                                           trading_pair="MX-USDT",
+                                           trade_type=TradeType.SELL,
+                                           price=Decimal("3.1504"),
+                                           amount=Decimal("6.3008"),
+                                           order_type=OrderType.LIMIT)
+
+        inflight_order = self.exchange.in_flight_orders["sell-MX-USDT-1638156451005305"]
+
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+
+        self.exchange._user_stream_tracker.user_stream.put_nowait(self.user_stream_data)
+        # Yield processor for the exchange to process the new message
+        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.3))
+
+        self.assertEqual("CANCELED", inflight_order.last_state)
+        self.assertTrue(inflight_order.is_cancelled)
+        self.assertFalse(inflight_order.client_order_id in self.exchange.in_flight_orders)
+        self.assertTrue(self._is_logged("INFO", f"Order {inflight_order.client_order_id} "
+                                                f"has been cancelled according to order delta websocket API."))
+        self.assertEqual(1, len(self.exchange.event_logs))
+        cancel_event = self.exchange.event_logs[0]
+        self.assertEqual(OrderCancelledEvent, type(cancel_event))
+        self.assertEqual(inflight_order.client_order_id, cancel_event.order_id)
+
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_order_event_with_rejected_status_makes_in_flight_order_fail(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        self.mocking_assistant.add_http_response(mock_api, 200, self.balances_mock_data)
+        self.exchange.start_tracking_order(order_id="sell-MX-USDT-1638156451005305",
+                                           exchange_order_id="40728558ead64032a676e6f0a4afc4ca",
+                                           trading_pair="MX-USDT",
+                                           trade_type=TradeType.SELL,
+                                           price=Decimal("3.1504"),
+                                           amount=Decimal("6.3008"),
+                                           order_type=OrderType.LIMIT)
+
+        inflight_order = self.exchange.in_flight_orders["sell-MX-USDT-1638156451005305"]
+
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+        stream_data = self.user_stream_data
+        stream_data.get("data")["status"] = 5
+        self.exchange._user_stream_tracker.user_stream.put_nowait(stream_data)
+        # Yield processor for the exchange to process the new message
+        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.3))
+
+        self.assertEqual("PARTIALLY_CANCELED", inflight_order.last_state)
+        self.assertTrue(inflight_order.is_failure)
+        self.assertFalse(inflight_order.client_order_id in self.exchange.in_flight_orders)
+        self.assertTrue(self._is_logged("INFO", f"Order {inflight_order.client_order_id} "
+                                                f"has been cancelled according to order delta websocket API."))
+        self.assertEqual(1, len(self.exchange.event_logs))
+        failure_event = self.exchange.event_logs[0]
+        self.assertEqual(OrderCancelledEvent, type(failure_event))
+        self.assertEqual(inflight_order.client_order_id, failure_event.order_id)
+
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_trade_event_fills_and_completes_buy_in_flight_order(self, mock_api):
+        fee_mock_data = {'code': 200, 'data': [{'id': 'c85b7062f69c4bf1b6c153dca5c0318a',
+                                                'symbol': 'MX_USDT', 'quantity': '2',
+                                                'price': '3.1265', 'amount': '6.253',
+                                                'fee': '0.012506', 'trade_type': 'BID',
+                                                'order_id': '95c4ce45fdd34cf99bfd1e1378eb38ae',
+                                                'is_taker': False, 'fee_currency': 'USDT',
+                                                'create_time': 1638177115000}]}
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        self.mocking_assistant.add_http_response(mock_api, 200, self.balances_mock_data)
+        self.mocking_assistant.add_http_response(mock_api, 200, fee_mock_data)
+        self.exchange.start_tracking_order(order_id="sell-MX-USDT-1638156451005305",
+                                           exchange_order_id="40728558ead64032a676e6f0a4afc4ca",
+                                           trading_pair="MX-USDT",
+                                           trade_type=TradeType.SELL,
+                                           price=Decimal("3.1504"),
+                                           amount=Decimal("6.3008"),
+                                           order_type=OrderType.LIMIT)
+        inflight_order = self.exchange.in_flight_orders["sell-MX-USDT-1638156451005305"]
+
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+        _user_stream = self.user_stream_data
+        _user_stream.get("data")["status"] = 2
+        self.exchange._user_stream_tracker.user_stream.put_nowait(_user_stream)
+        # Yield processor for the exchange to process the new message
+        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.3))
+
+        self.assertEqual("FILLED", inflight_order.last_state)
+        self.assertEqual(Decimal(0), inflight_order.executed_amount_base)
+        self.assertEqual(Decimal(0), inflight_order.executed_amount_quote)
+        self.assertEqual(1, len(self.exchange.event_logs))
+        fill_event = self.exchange.event_logs[0]
+        self.assertEqual(SellOrderCompletedEvent, type(fill_event))
+        self.assertEqual(inflight_order.client_order_id, fill_event.order_id)
+        self.assertEqual(inflight_order.trading_pair, f'{fill_event.base_asset}-{fill_event.quote_asset}')
+
+    def test_tick_initial_tick_successful(self):
+        start_ts: float = time.time() * 1e3
+
+        self.exchange.tick(start_ts)
+        self.assertEqual(start_ts, self.exchange._last_timestamp)
+        self.assertTrue(self.exchange._poll_notifier.is_set())
+
+    @patch("time.time")
+    def test_tick_subsequent_tick_within_short_poll_interval(self, mock_ts):
+        # Assumes user stream tracker has NOT been receiving messages, Hence SHORT_POLL_INTERVAL in use
+        start_ts: float = self.start_timestamp
+        next_tick: float = start_ts + (self.exchange.SHORT_POLL_INTERVAL - 1)
+
+        mock_ts.return_value = start_ts
+        self.exchange.tick(start_ts)
+        self.assertEqual(start_ts, self.exchange._last_timestamp)
+        self.assertTrue(self.exchange._poll_notifier.is_set())
+
+        self._simulate_reset_poll_notifier()
+
+        mock_ts.return_value = next_tick
+        self.exchange.tick(next_tick)
+        self.assertEqual(next_tick, self.exchange._last_timestamp)
+        self.assertTrue(self.exchange._poll_notifier.is_set())
+
+    @patch("time.time")
+    def test_tick_subsequent_tick_exceed_short_poll_interval(self, mock_ts):
+        # Assumes user stream tracker has NOT been receiving messages, Hence SHORT_POLL_INTERVAL in use
+        start_ts: float = self.start_timestamp
+        next_tick: float = start_ts + (self.exchange.SHORT_POLL_INTERVAL + 1)
+
+        mock_ts.return_value = start_ts
+        self.exchange.tick(start_ts)
+        self.assertEqual(start_ts, self.exchange._last_timestamp)
+        self.assertTrue(self.exchange._poll_notifier.is_set())
+
+        self._simulate_reset_poll_notifier()
+
+        mock_ts.return_value = next_tick
+        self.exchange.tick(next_tick)
+        self.assertEqual(next_tick, self.exchange._last_timestamp)
+        self.assertTrue(self.exchange._poll_notifier.is_set())
+
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_update_balances(self, mock_api):
+        self.assertEqual(0, len(self.exchange._account_balances))
+        self.assertEqual(0, len(self.exchange._account_available_balances))
+
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        self.mocking_assistant.add_http_response(mock_api, 200, self.balances_mock_data, "")
+
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._update_balances()
+        )
+        asyncio.get_event_loop().run_until_complete(self.exchange_task)
+
+        self.assertEqual(Decimal(str(481.0)), self.exchange.get_balance(self.base_asset))
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange.current_timestamp", new_callable=PropertyMock)
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    @patch("aiohttp.ClientSession.get", new_callable=AsyncMock)
+    def test_update_order_status(self, mock_order_status, mock_trade_history, mock_ts):
+        # Simulates order being tracked
+        order: MexcInFlightOrder = MexcInFlightOrder(
+            "0",
+            "2628",
+            self.trading_pair,
+            OrderType.LIMIT,
+            TradeType.SELL,
+            Decimal(str(41720.83)),
+            Decimal("1"),
+            "Working",
+        )
+        self.exchange._in_flight_orders.update({
+            order.client_order_id: order
+        })
+        self.exchange._last_poll_timestamp = 10
+        ts: float = time.time()
+        mock_ts.return_value = ts
+        self.exchange._current_timestamp = ts
+        self.assertTrue(1, len(self.exchange.in_flight_orders))
+
+        # Add FullyExecuted GetOrderStatus API Response
+        self.mocking_assistant.configure_http_request_mock(mock_order_status)
+        self.mocking_assistant.add_http_response(
+            mock_order_status,
+            200,
+            {
+                "Side": "Sell",
+                "OrderId": 2628,
+                "Price": 41720.830000000000000000000000,
+                "Quantity": 0.0000000000000000000000000000,
+                "DisplayQuantity": 0.0000000000000000000000000000,
+                "Instrument": 5,
+                "Account": 528,
+                "AccountName": "hbot",
+                "OrderType": "Limit",
+                "ClientOrderId": 0,
+                "OrderState": "FullyExecuted",
+                "ReceiveTime": 1627380780887,
+                "ReceiveTimeTicks": 637629775808866338,
+                "LastUpdatedTime": 1627380783860,
+                "LastUpdatedTimeTicks": 637629775838598558,
+                "OrigQuantity": 1.0000000000000000000000000000,
+                "QuantityExecuted": 1.0000000000000000000000000000,
+                "GrossValueExecuted": 41720.830000000000000000000000,
+                "ExecutableValue": 0.0000000000000000000000000000,
+                "AvgPrice": 41720.830000000000000000000000,
+                "CounterPartyId": 0,
+                "ChangeReason": "Trade",
+                "OrigOrderId": 2628,
+                "OrigClOrdId": 0,
+                "EnteredBy": 492,
+                "UserName": "hbot",
+                "IsQuote": False,
+                "InsideAsk": 41720.830000000000000000000000,
+                "InsideAskSize": 0.9329960000000000000000000000,
+                "InsideBid": 41718.340000000000000000000000,
+                "InsideBidSize": 0.0632560000000000000000000000,
+                "LastTradePrice": 41720.830000000000000000000000,
+                "RejectReason": "",
+                "IsLockedIn": False,
+                "CancelReason": "",
+                "OrderFlag": "AddedToBook, RemovedFromBook",
+                "UseMargin": False,
+                "StopPrice": 0.0000000000000000000000000000,
+                "PegPriceType": "Last",
+                "PegOffset": 0.0000000000000000000000000000,
+                "PegLimitOffset": 0.0000000000000000000000000000,
+                "IpAddress": "103.6.151.12",
+                "ClientOrderIdUuid": None,
+                "OMSId": 1
+            },
+            "")
+
+        # Add TradeHistory API Response
+        self.mocking_assistant.configure_http_request_mock(mock_trade_history)
+        self.mocking_assistant.add_http_response(
+            mock_trade_history,
+            200,
+            {
+                "code": 200,
+                "data": [
+                    {
+                        "id": "504feca6ba6349e39c82262caf0be3f4",
+                        "symbol": "MX_USDT",
+                        "price": "3.001",
+                        "quantity": "30",
+                        "state": "CANCELED",
+                        "type": "BID",
+                        "deal_quantity": "0",
+                        "deal_amount": "0",
+                        "create_time": 1573117266000
+                    }
+                ]
+            },
+            "")
+
+        self.exchange_task = asyncio.get_event_loop().create_task(self.exchange._update_order_status())
+        asyncio.get_event_loop().run_until_complete(self.exchange_task)
+        self.assertEqual(0, len(self.exchange.in_flight_orders))
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange.current_timestamp", new_callable=PropertyMock)
+    @patch("aiohttp.ClientSession.get", new_callable=AsyncMock)
+    def test_update_order_status_error_response(self, mock_api, mock_ts):
+
+        # Simulates order being tracked
+        order: MexcInFlightOrder = MexcInFlightOrder("0", "2628", self.trading_pair, OrderType.LIMIT, TradeType.SELL,
+                                                     Decimal(str(41720.83)), Decimal("1"))
+        self.exchange._in_flight_orders.update({
+            order.client_order_id: order
+        })
+        self.assertTrue(1, len(self.exchange.in_flight_orders))
+
+        ts: float = time.time()
+        mock_ts.return_value = ts
+        self.exchange._current_timestamp = ts
+        # Add FullyExecuted GetOrderStatus API Response
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        self.mocking_assistant.add_http_response(
+            mock_api,
+            200,
+            {
+                "Side": "Sell",
+                "OrderId": 2628,
+                "Price": 41720.830000000000000000000000,
+                "Quantity": 0.0000000000000000000000000000,
+                "DisplayQuantity": 0.0000000000000000000000000000,
+                "Instrument": 5,
+                "Account": 528,
+                "AccountName": "hbot",
+                "OrderType": "Limit",
+                "ClientOrderId": 0,
+                "OrderState": "Working",
+                "ReceiveTime": 1627380780887,
+                "ReceiveTimeTicks": 637629775808866338,
+                "LastUpdatedTime": 1627380783860,
+                "LastUpdatedTimeTicks": 637629775838598558,
+                "OrigQuantity": 1.0000000000000000000000000000,
+                "QuantityExecuted": 1.0000000000000000000000000000,
+                "GrossValueExecuted": 41720.830000000000000000000000,
+                "ExecutableValue": 0.0000000000000000000000000000,
+                "AvgPrice": 41720.830000000000000000000000,
+                "CounterPartyId": 0,
+                "ChangeReason": "Trade",
+                "OrigOrderId": 2628,
+                "OrigClOrdId": 0,
+                "EnteredBy": 492,
+                "UserName": "hbot",
+                "IsQuote": False,
+                "InsideAsk": 41720.830000000000000000000000,
+                "InsideAskSize": 0.9329960000000000000000000000,
+                "InsideBid": 41718.340000000000000000000000,
+                "InsideBidSize": 0.0632560000000000000000000000,
+                "LastTradePrice": 41720.830000000000000000000000,
+                "RejectReason": "",
+                "IsLockedIn": False,
+                "CancelReason": "",
+                "OrderFlag": "AddedToBook, RemovedFromBook",
+                "UseMargin": False,
+                "StopPrice": 0.0000000000000000000000000000,
+                "PegPriceType": "Last",
+                "PegOffset": 0.0000000000000000000000000000,
+                "PegLimitOffset": 0.0000000000000000000000000000,
+                "IpAddress": "103.6.151.12",
+                "ClientOrderIdUuid": None,
+                "OMSId": 1
+            },
+            "")
+
+        # Add TradeHistory API Response
+        self.mocking_assistant.add_http_response(
+            mock_api,
+            200,
+            {
+                "result": False,
+                "errormsg": "Invalid Request",
+                "errorcode": 100,
+                "detail": None
+            }, "")
+        self.exchange_task = asyncio.get_event_loop().create_task(self.exchange._update_order_status())
+        asyncio.get_event_loop().run_until_complete(self.exchange_task)
+        self.assertEqual(1, len(self.exchange.in_flight_orders))
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange._update_balances", new_callable=AsyncMock)
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange._update_order_status", new_callable=AsyncMock)
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange.current_timestamp", new_callable=PropertyMock)
+    def test_status_polling_loop(self, mock_ts, mock_update_order_status, mock_balances):
+        mock_balances.return_value = None
+        mock_update_order_status.return_value = None
+
+        ts: float = time.time()
+        mock_ts.return_value = ts
+        self.exchange._current_timestamp = ts
+
+        with self.assertRaises(asyncio.TimeoutError):
+            self.exchange_task = asyncio.get_event_loop().create_task(
+                self.exchange._status_polling_loop()
+            )
+
+            # Add small delay to ensure an API request is "called"
+            asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.5))
+            self.exchange._poll_notifier.set()
+
+            asyncio.get_event_loop().run_until_complete(asyncio.wait_for(self.exchange_task, 2.0))
+
+        self.assertEqual(ts, self.exchange._last_poll_timestamp)
+
+    @patch("aiohttp.ClientSession.request")
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange.current_timestamp", new_callable=PropertyMock)
+    def test_status_polling_loop_cancels(self, mock_ts, mock_api):
+        mock_api.side_effect = asyncio.CancelledError
+
+        ts: float = time.time()
+        mock_ts.return_value = ts
+        self.exchange._current_timestamp = ts
+
+        with self.assertRaises(asyncio.CancelledError):
+            self.exchange_task = asyncio.get_event_loop().create_task(
+                self.exchange._status_polling_loop()
+            )
+            # Add small delay to ensure _poll_notifier is set
+            asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.5))
+            self.exchange._poll_notifier.set()
+
+            asyncio.get_event_loop().run_until_complete(self.exchange_task)
+
+        self.assertEqual(0, self.exchange._last_poll_timestamp)
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange._update_balances", new_callable=AsyncMock)
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange._update_order_status", new_callable=AsyncMock)
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange.current_timestamp", new_callable=PropertyMock)
+    def test_status_polling_loop_exception_raised(self, mock_ts, mock_update_order_status, mock_balances):
+        mock_balances.side_effect = lambda: self._create_exception_and_unlock_test_with_event(
+            Exception("Dummy test error"))
+        mock_update_order_status.side_effect = lambda: self._create_exception_and_unlock_test_with_event(
+            Exception("Dummy test error"))
+
+        ts: float = time.time()
+        mock_ts.return_value = ts
+        self.exchange._current_timestamp = ts
+
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._status_polling_loop()
+        )
+
+        # Add small delay to ensure an API request is "called"
+        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.5))
+        self.exchange._poll_notifier.set()
+
+        asyncio.get_event_loop().run_until_complete(self.resume_test_event.wait())
+
+        self.assertEqual(0, self.exchange._last_poll_timestamp)
+        self._is_logged("ERROR", "Unexpected error while in status polling loop. Error: ")
+
+    def test_format_trading_rules_success(self):
+        instrument_info: List[Dict[str, Any]] = [{
+            "symbol": f"{self.base_asset}_{self.quote_asset}",
+            "price_scale": 3,
+            "quantity_scale": 3,
+            "min_amount": "1",
+        }]
+
+        result: List[str, TradingRule] = self.exchange._format_trading_rules(instrument_info)
+        self.assertTrue(self.trading_pair == result[0].trading_pair)
+
+    def test_format_trading_rules_failure(self):
+        # Simulate invalid API response
+        instrument_info: List[Dict[str, Any]] = [{}]
+
+        result: Dict[str, TradingRule] = self.exchange._format_trading_rules(instrument_info)
+        self.assertTrue(self.trading_pair not in result)
+        self.assertTrue(self._is_logged("ERROR", 'Error parsing the trading pair rule {}. Skipping.'))
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange.current_timestamp", new_callable=PropertyMock)
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_update_trading_rules(self, mock_api, mock_ts):
+        mock_response = {
+            "code": 200,
+            "data": [
+                {
+                    "symbol": "MX_USDT",
+                    "state": "ENABLED",
+                    "price_scale": 4,
+                    "quantity_scale": 2,
+                    "min_amount": "5",
+                    "max_amount": "5000000",
+                    "maker_fee_rate": "0.002",
+                    "taker_fee_rate": "0.002",
+                    "limited": False,
+                    "etf_mark": 0,
+                    "symbol_partition": "MAIN"
+                }
+            ]
+        }
+        self.exchange._last_poll_timestamp = 10
+        ts: float = time.time()
+        mock_ts.return_value = ts
+        self.exchange._current_timestamp = ts
+
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response, "")
+
+        task = asyncio.get_event_loop().create_task(
+            self.exchange._update_trading_rules()
+        )
+        asyncio.get_event_loop().run_until_complete(task)
+
+        self.assertTrue(self.trading_pair in self.exchange.trading_rules)
+
+        self.exchange.trading_rules[self.trading_pair]
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange._update_trading_rules",
+           new_callable=AsyncMock)
+    def test_trading_rules_polling_loop(self, mock_update):
+        # No Side Effects expected
+        mock_update.return_value = None
+        with self.assertRaises(asyncio.TimeoutError):
+            self.exchange_task = asyncio.get_event_loop().create_task(self.exchange._trading_rules_polling_loop())
+
+            asyncio.get_event_loop().run_until_complete(
+                asyncio.wait_for(self.exchange_task, 1.0)
+            )
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange._update_trading_rules",
+           new_callable=AsyncMock)
+    def test_trading_rules_polling_loop_cancels(self, mock_update):
+        mock_update.side_effect = asyncio.CancelledError
+
+        with self.assertRaises(asyncio.CancelledError):
+            self.exchange_task = asyncio.get_event_loop().create_task(
+                self.exchange._trading_rules_polling_loop()
+            )
+
+            asyncio.get_event_loop().run_until_complete(self.exchange_task)
+
+        self.assertEqual(0, self.exchange._last_poll_timestamp)
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange._update_trading_rules",
+           new_callable=AsyncMock)
+    def test_trading_rules_polling_loop_exception_raised(self, mock_update):
+        mock_update.side_effect = lambda: self._create_exception_and_unlock_test_with_event(
+            Exception("Dummy test error"))
+
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._trading_rules_polling_loop()
+        )
+
+        asyncio.get_event_loop().run_until_complete(self.resume_test_event.wait())
+
+        self._is_logged("ERROR", "Unexpected error while fetching trading rules. Error: ")
+
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_check_network_succeeds_when_ping_replies_pong(self, mock_api):
+        mock_response = {"code": 200}
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response, "")
+
+        result = asyncio.get_event_loop().run_until_complete(self.exchange.check_network())
+
+        self.assertEqual(NetworkStatus.CONNECTED, result)
+
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_check_network_fails_when_ping_does_not_reply_pong(self, mock_api):
+        mock_response = {"code": 100}
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response, "")
+
+        result = asyncio.get_event_loop().run_until_complete(self.exchange.check_network())
+        self.assertEqual(NetworkStatus.NOT_CONNECTED, result)
+
+        mock_response = {}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response, "")
+
+        result = asyncio.get_event_loop().run_until_complete(self.exchange.check_network())
+        self.assertEqual(NetworkStatus.NOT_CONNECTED, result)
+
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_check_network_fails_when_ping_returns_error_code(self, mock_api):
+        mock_response = {"code": 200}
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        self.mocking_assistant.add_http_response(mock_api, 404, mock_response, "")
+
+        result = asyncio.get_event_loop().run_until_complete(self.exchange.check_network())
+
+        self.assertEqual(NetworkStatus.NOT_CONNECTED, result)
+
+    def test_get_order_book_for_valid_trading_pair(self):
+        dummy_order_book = MexcOrderBook()
+        self.exchange._order_book_tracker.order_books["BTC-USDT"] = dummy_order_book
+        self.assertEqual(dummy_order_book, self.exchange.get_order_book("BTC-USDT"))
+
+    def test_get_order_book_for_invalid_trading_pair_raises_error(self):
+        self.assertRaisesRegex(ValueError,
+                               "No order book exists for 'BTC-USDT'",
+                               self.exchange.get_order_book,
+                               "BTC-USDT")
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange.execute_buy", new_callable=AsyncMock)
+    def test_buy(self, mock_create):
+        mock_create.side_effect = None
+        order_details = [
+            self.trading_pair,
+            Decimal(1.0),
+            Decimal(10.0),
+            OrderType.LIMIT,
+        ]
+
+        # Note: BUY simply returns immediately with the client order id.
+        order_id: str = self.exchange.buy(*order_details)
+
+        # Order ID is simply a timestamp. The assertion below checks if it is created within 1 sec
+        self.assertTrue(len(order_id) > 0)
+
+    def test_sell(self):
+        order_details = [
+            self.trading_pair,
+            Decimal(1.0),
+            Decimal(10.0),
+            OrderType.LIMIT,
+        ]
+
+        # Note: SELL simply returns immediately with the client order id.
+        order_id: str = self.exchange.buy(*order_details)
+
+        # Order ID is simply a timestamp. The assertion below checks if it is created within 1 sec
+        self.assertTrue(len(order_id) > 0)
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange.quantize_order_amount")
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_create_limit_order(self, mock_post, amount_mock):
+        amount_mock.return_value = Decimal("1")
+        expected_response = {"code": 200, "data": "123"}
+        self.mocking_assistant.configure_http_request_mock(mock_post)
+        self.mocking_assistant.add_http_response(mock_post, 200, expected_response, "")
+
+        self._simulate_trading_rules_initialized()
+
+        order_details = [
+            TradeType.BUY,
+            str(1),
+            self.trading_pair,
+            Decimal(1.0),
+            Decimal(10.0),
+            OrderType.LIMIT,
+        ]
+
+        self.assertEqual(0, len(self.exchange.in_flight_orders))
+        future = self._simulate_create_order(*order_details)
+        asyncio.get_event_loop().run_until_complete(future)
+
+        self.assertEqual(1, len(self.exchange.in_flight_orders))
+        self._is_logged("INFO",
+                        f"Created {OrderType.LIMIT.name} {TradeType.BUY.name} order {123} for {Decimal(1.0)} {self.trading_pair}")
+
+        tracked_order: MexcInFlightOrder = self.exchange.in_flight_orders["1"]
+        self.assertEqual(tracked_order.client_order_id, "1")
+        self.assertEqual(tracked_order.exchange_order_id, "123")
+        self.assertEqual(tracked_order.last_state, "NEW")
+        self.assertEqual(tracked_order.trading_pair, self.trading_pair)
+        self.assertEqual(tracked_order.price, Decimal(10.0))
+        self.assertEqual(tracked_order.amount, Decimal(1.0))
+        self.assertEqual(tracked_order.trade_type, TradeType.BUY)
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange.quantize_order_amount")
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_create_market_order(self, mock_post, amount_mock):
+        amount_mock.return_value = Decimal("1")
+        expected_response = {"code": 200, "data": "123"}
+        self.mocking_assistant.configure_http_request_mock(mock_post)
+        self.mocking_assistant.add_http_response(mock_post, 200, expected_response, "")
+
+        self._simulate_trading_rules_initialized()
+
+        order_details = [
+            TradeType.BUY,
+            str(1),
+            self.trading_pair,
+            Decimal(1.0),
+            Decimal(10.0),
+            OrderType.LIMIT_MAKER,
+        ]
+
+        self.assertEqual(0, len(self.exchange.in_flight_orders))
+        future = self._simulate_create_order(*order_details)
+        asyncio.get_event_loop().run_until_complete(future)
+
+        self.assertEqual(1, len(self.exchange.in_flight_orders))
+        self._is_logged("INFO",
+                        f"Created {OrderType.LIMIT.name} {TradeType.BUY.name} order {123} for {Decimal(1.0)} {self.trading_pair}")
+
+        tracked_order: MexcInFlightOrder = self.exchange.in_flight_orders["1"]
+        self.assertEqual(tracked_order.client_order_id, "1")
+        self.assertEqual(tracked_order.exchange_order_id, "123")
+        self.assertEqual(tracked_order.last_state, "NEW")
+        self.assertEqual(tracked_order.trading_pair, self.trading_pair)
+        self.assertEqual(tracked_order.amount, Decimal(1.0))
+        self.assertEqual(tracked_order.trade_type, TradeType.BUY)
+
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_detect_created_order_server_acknowledgement(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        self.mocking_assistant.add_http_response(mock_api, 200, self.balances_mock_data, self.balances_mock_data)
+
+        self.exchange.start_tracking_order(order_id="sell-MX-USDT-1638156451005305",
+                                           exchange_order_id="40728558ead64032a676e6f0a4afc4ca",
+                                           trading_pair="MX-USDT",
+                                           trade_type=TradeType.SELL,
+                                           price=Decimal("3.1504"),
+                                           amount=Decimal("6.3008"),
+                                           order_type=OrderType.LIMIT)
+
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+        _user_data = self.user_stream_data
+        _user_data.get("data")["status"] = 2
+        self.exchange._user_stream_tracker.user_stream.put_nowait(_user_data)
+        # Yield processor for the exchange to process the new message
+        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.3))
+
+        self.assertEqual(1, len(self.exchange.in_flight_orders))
+        tracked_order: MexcInFlightOrder = self.exchange.in_flight_orders["sell-MX-USDT-1638156451005305"]
+        self.assertEqual(tracked_order.last_state, "NEW")
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange.quantize_order_amount")
+    @patch("hummingbot.client.hummingbot_application.HummingbotApplication")
+    def test_create_order_below_min_order_size_exception_raised(self, mock_main_app, amount_mock):
+        amount_mock.return_value = Decimal("1")
+        self._simulate_trading_rules_initialized()
+
+        order_details = [
+            str(1),
+            self.trading_pair,
+            Decimal(1.0),
+            OrderType.LIMIT_MAKER,
+            Decimal(10.0),
+        ]
+
+        self.assertEqual(0, len(self.exchange.in_flight_orders))
+
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange.execute_buy(*order_details)
+        )
+
+        asyncio.get_event_loop().run_until_complete(self.exchange_task)
+
+        self.assertEqual(0, len(self.exchange.in_flight_orders))
+        self._is_logged("NETWORK", "Error submitting buy limit_maker order to Mexc for 1 MX-USDT "
+                                   ".10.0000.OSError(\"Error request from https://www.mexc.com/open/api/v2/order/place?"
+                                   "api_key=testAPIKey&req_time=1638244964&sign=7c9b991595d647c326685680fef3f26b5b273527"
+                                   "bd9180fe92d080d74b917231. Response: {'msg': 'invalid api_key', 'code': 400}.\")")
+
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_execute_cancel_success(self, mock_cancel):
+        order: MexcInFlightOrder = MexcInFlightOrder(
+            client_order_id="0",
+            exchange_order_id="123",
+            trading_pair=self.trading_pair,
+            order_type=OrderType.LIMIT,
+            trade_type=TradeType.BUY,
+            price=Decimal(10.0),
+            amount=Decimal(1.0),
+            initial_state="Working",
+        )
+
+        self.exchange._in_flight_orders.update({
+            order.client_order_id: order
+        })
+
+        mock_response = {
+            "code": 200,
+            "data": {"123": "success"}
+        }
+
+        self.mocking_assistant.configure_http_request_mock(mock_cancel)
+        self.mocking_assistant.add_http_response(mock_cancel, 200, mock_response, "")
+
+        result = asyncio.new_event_loop().run_until_complete(
+            self.exchange.execute_cancel(self.trading_pair, order.client_order_id)
+        )
+        self.assertIsNone(result)
+
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_execute_cancel_all_success(self, mock_post_request):
+        order: MexcInFlightOrder = MexcInFlightOrder(
+            client_order_id="0",
+            exchange_order_id="123",
+            trading_pair=self.trading_pair,
+            order_type=OrderType.LIMIT,
+            trade_type=TradeType.BUY,
+            price=Decimal(10.0),
+            amount=Decimal(1.0))
+
+        self.exchange._in_flight_orders.update({
+            order.client_order_id: order
+        })
+
+        mock_response = {
+            "code": 200,
+            "data": {
+                "0": "success"
+            }
+        }
+        self.mocking_assistant.configure_http_request_mock(mock_post_request)
+        self.mocking_assistant.add_http_response(mock_post_request, 200, mock_response, "")
+
+        cancellation_results = asyncio.new_event_loop().run_until_complete(
+            self.exchange.cancel_all(10)
+        )
+
+        self.assertEqual(1, len(cancellation_results))
+        self.assertEqual("0", cancellation_results[0].order_id)
+        self.assertTrue(cancellation_results[0].success)
+
+    @patch("hummingbot.client.hummingbot_application.HummingbotApplication")
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_execute_cancel_fail(self, mock_cancel, mock_main_app):
+        order: MexcInFlightOrder = MexcInFlightOrder(
+            client_order_id="0",
+            exchange_order_id="123",
+            trading_pair=self.trading_pair,
+            order_type=OrderType.LIMIT,
+            trade_type=TradeType.BUY,
+            price=Decimal(10.0),
+            amount=Decimal(1.0),
+            initial_state="Working",
+        )
+
+        self.exchange._in_flight_orders.update({
+            order.client_order_id: order
+        })
+        mock_response = {
+            "code": 100,
+            "data": {"123": "success"}
+        }
+
+        self.mocking_assistant.configure_http_request_mock(mock_cancel)
+        self.mocking_assistant.add_http_response(mock_cancel, 200, mock_response, "")
+
+        asyncio.new_event_loop().run_until_complete(
+            self.exchange.execute_cancel(self.trading_pair, order.client_order_id)
+        )
+
+        self._is_logged("NETWORK", "Failed to cancel order 0 : MexcAPIError('Order could not be canceled')")
+
+    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    def test_execute_cancel_cancels(self, mock_cancel):
+        order: MexcInFlightOrder = MexcInFlightOrder(
+            client_order_id="0",
+            exchange_order_id="123",
+            trading_pair=self.trading_pair,
+            order_type=OrderType.LIMIT,
+            trade_type=TradeType.BUY,
+            price=Decimal(10.0),
+            amount=Decimal(1.0),
+            initial_state="Working",
+        )
+
+        self.exchange._in_flight_orders.update({
+            order.client_order_id: order
+        })
+
+        mock_cancel.side_effect = asyncio.CancelledError
+
+        with self.assertRaises(asyncio.CancelledError):
+            asyncio.new_event_loop().run_until_complete(
+                self.exchange.execute_cancel(self.trading_pair, order.client_order_id)
+            )
+
+    @patch("hummingbot.connector.exchange.mexc.mexc_exchange.MexcExchange.execute_cancel", new_callable=AsyncMock)
+    def test_cancel(self, mock_cancel):
+        mock_cancel.return_value = None
+
+        order: MexcInFlightOrder = MexcInFlightOrder(
+            client_order_id="0",
+            exchange_order_id="123",
+            trading_pair=self.trading_pair,
+            order_type=OrderType.LIMIT,
+            trade_type=TradeType.BUY,
+            price=Decimal(10.0),
+            amount=Decimal(1.0))
+
+        self.exchange._in_flight_orders.update({
+            order.client_order_id: order
+        })
+
+        # Note: BUY simply returns immediately with the client order id.
+        return_val: str = self.exchange.cancel(self.trading_pair, order.client_order_id)
+
+        # Order ID is simply a timestamp. The assertion below checks if it is created within 1 sec
+        self.assertTrue(order.client_order_id, return_val)
+
+    def test_ready_trading_required_all_ready(self):
+        self.exchange._trading_required = True
+
+        # Simulate all components initialized
+        self.exchange._account_id = 1
+        self.exchange._order_book_tracker._order_books_initialized.set()
+        self.exchange._account_balances = {
+            self.base_asset: Decimal(str(10.0))
+        }
+        self._simulate_trading_rules_initialized()
+        self.exchange._user_stream_tracker.data_source._last_recv_time = 1
+
+        self.assertTrue(self.exchange.ready)
+
+    def test_ready_trading_required_not_ready(self):
+        self.exchange._trading_required = True
+
+        # Simulate all components but account_id not initialized
+        self.exchange._account_id = None
+        self.exchange._order_book_tracker._order_books_initialized.set()
+        self.exchange._account_balances = {}
+        self._simulate_trading_rules_initialized()
+        self.exchange._user_stream_tracker.data_source._last_recv_time = 0
+
+        self.assertFalse(self.exchange.ready)
+
+    def test_ready_trading_not_required_ready(self):
+        self.exchange._trading_required = False
+
+        # Simulate all components but account_id not initialized
+        self.exchange._account_id = None
+        self.exchange._order_book_tracker._order_books_initialized.set()
+        self.exchange._account_balances = {}
+        self._simulate_trading_rules_initialized()
+        self.exchange._user_stream_tracker.data_source._last_recv_time = 0
+
+        self.assertTrue(self.exchange.ready)
+
+    def test_ready_trading_not_required_not_ready(self):
+        self.exchange._trading_required = False
+        self.assertFalse(self.exchange.ready)
+
+    def test_limit_orders(self):
+        self.assertEqual(0, len(self.exchange.limit_orders))
+
+        # Simulate orders being placed and tracked
+        order: MexcInFlightOrder = MexcInFlightOrder(
+            client_order_id="0",
+            exchange_order_id="123",
+            trading_pair=self.trading_pair,
+            order_type=OrderType.LIMIT,
+            trade_type=TradeType.BUY,
+            price=Decimal(10.0),
+            amount=Decimal(1.0))
+
+        self.exchange._in_flight_orders.update({
+            order.client_order_id: order
+        })
+
+        self.assertEqual(1, len(self.exchange.limit_orders))
+
+    def test_tracking_states_order_not_done(self):
+        order: MexcInFlightOrder = MexcInFlightOrder(
+            client_order_id="0",
+            exchange_order_id="123",
+            trading_pair=self.trading_pair,
+            order_type=OrderType.LIMIT,
+            trade_type=TradeType.BUY,
+            price=Decimal(10.0),
+            amount=Decimal(1.0))
+
+        order_json = order.to_json()
+
+        self.exchange._in_flight_orders.update({
+            order.client_order_id: order
+        })
+
+        self.assertEqual(1, len(self.exchange.tracking_states))
+        self.assertEqual(order_json, self.exchange.tracking_states[order.client_order_id])
+
+    def test_tracking_states_order_done(self):
+        order: MexcInFlightOrder = MexcInFlightOrder(
+            client_order_id="0",
+            exchange_order_id="123",
+            trading_pair=self.trading_pair,
+            order_type=OrderType.LIMIT,
+            trade_type=TradeType.BUY,
+            price=Decimal(10.0),
+            amount=Decimal(1.0),
+            initial_state="FILLED"
+        )
+
+        self.exchange._in_flight_orders.update({
+            order.client_order_id: order
+        })
+
+        self.assertEqual(0, len(self.exchange.tracking_states))
+
+    def test_restore_tracking_states(self):
+        order: MexcInFlightOrder = MexcInFlightOrder(
+            client_order_id="0",
+            exchange_order_id="123",
+            trading_pair=self.trading_pair,
+            order_type=OrderType.LIMIT,
+            trade_type=TradeType.BUY,
+            price=Decimal(10.0),
+            amount=Decimal(1.0))
+
+        order_json = order.to_json()
+
+        self.exchange.restore_tracking_states({order.client_order_id: order_json})
+
+        self.assertEqual(1, len(self.exchange.in_flight_orders))
+        self.assertEqual(str(self.exchange.in_flight_orders[order.client_order_id]), str(order))
diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_in_flight_order.py b/test/hummingbot/connector/exchange/mexc/test_mexc_in_flight_order.py
new file mode 100644
index 00000000000..e70c97b0e65
--- /dev/null
+++ b/test/hummingbot/connector/exchange/mexc/test_mexc_in_flight_order.py
@@ -0,0 +1,97 @@
+from decimal import Decimal
+from unittest import TestCase
+
+from hummingbot.connector.exchange.mexc.mexc_in_flight_order import \
+    MexcInFlightOrder
+from hummingbot.core.event.events import OrderType, TradeType
+
+
+class MexcInFlightOrderTests(TestCase):
+
+    def _example_json(self):
+        return {"client_order_id": "C1",
+                "exchange_order_id": "1",
+                "trading_pair": "BTC-USDT",
+                "order_type": "LIMIT",
+                "trade_type": "BUY",
+                "price": "35000",
+                "amount": "1.1",
+                "last_state": "Working",
+                "executed_amount_base": "0.5",
+                "executed_amount_quote": "15000",
+                "fee_asset": "BTC",
+                "fee_paid": "0"}
+
+    def test_instance_creation(self):
+        order = MexcInFlightOrder(client_order_id="C1",
+                                  exchange_order_id="1",
+                                  trading_pair="BTC-USDT",
+                                  order_type=OrderType.LIMIT,
+                                  trade_type=TradeType.SELL,
+                                  price=Decimal("35000"),
+                                  amount=Decimal("1.1"))
+
+        self.assertEqual("C1", order.client_order_id)
+        self.assertEqual("1", order.exchange_order_id)
+        self.assertEqual("BTC-USDT", order.trading_pair)
+        self.assertEqual(OrderType.LIMIT, order.order_type)
+        self.assertEqual(TradeType.SELL, order.trade_type)
+        self.assertEqual(Decimal("35000"), order.price)
+        self.assertEqual(Decimal("1.1"), order.amount)
+        self.assertEqual(Decimal("0"), order.executed_amount_base)
+        self.assertEqual(Decimal("0"), order.executed_amount_quote)
+        self.assertEqual(order.quote_asset, order.fee_asset)
+        self.assertEqual(Decimal("0"), order.fee_paid)
+
+    def test_create_from_json(self):
+        order = MexcInFlightOrder.from_json(self._example_json())
+
+        self.assertEqual("C1", order.client_order_id)
+        self.assertEqual("1", order.exchange_order_id)
+        self.assertEqual("BTC-USDT", order.trading_pair)
+        self.assertEqual(OrderType.LIMIT, order.order_type)
+        self.assertEqual(TradeType.BUY, order.trade_type)
+        self.assertEqual(Decimal("35000"), order.price)
+        self.assertEqual(Decimal("1.1"), order.amount)
+        self.assertEqual(Decimal("0.5"), order.executed_amount_base)
+        self.assertEqual(Decimal("15000"), order.executed_amount_quote)
+        self.assertEqual(order.base_asset, order.fee_asset)
+        self.assertEqual(Decimal("0"), order.fee_paid)
+        self.assertEqual("Working", order.last_state)
+
+    def test_is_done(self):
+        order = MexcInFlightOrder.from_json(self._example_json())
+
+        self.assertFalse(order.is_done)
+
+        for status in ["FILLED", "CANCELED", "PARTIALLY_CANCELED"]:
+            order.last_state = status
+            self.assertTrue(order.is_done)
+
+    def test_is_failure(self):
+        order = MexcInFlightOrder.from_json(self._example_json())
+
+        for status in ["NEW", "PARTIALLY_FILLED"]:
+            order.last_state = status
+            self.assertFalse(order.is_failure)
+
+        # order.last_state = "Rejected"
+        # self.assertTrue(order.is_failure)
+
+    def test_is_cancelled(self):
+        order = MexcInFlightOrder.from_json(self._example_json())
+
+        for status in ["Working", "FullyExecuted", "Rejected"]:
+            order.last_state = status
+            self.assertFalse(order.is_cancelled)
+
+    def test_mark_as_filled(self):
+        order = MexcInFlightOrder.from_json(self._example_json())
+
+        order.mark_as_filled()
+        self.assertEqual("FILLED", order.last_state)
+
+    def test_to_json(self):
+        order = MexcInFlightOrder.from_json(self._example_json())
+
+        self.assertEqual(self._example_json(), order.to_json())
diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_order_book_message.py b/test/hummingbot/connector/exchange/mexc/test_mexc_order_book_message.py
new file mode 100644
index 00000000000..556e30dfc01
--- /dev/null
+++ b/test/hummingbot/connector/exchange/mexc/test_mexc_order_book_message.py
@@ -0,0 +1,67 @@
+from unittest import TestCase
+
+from hummingbot.connector.exchange.mexc.mexc_order_book_message import MexcOrderBookMessage
+from hummingbot.core.data_type.order_book_message import OrderBookMessageType
+
+
+class MexcOrderBookMessageTests(TestCase):
+
+    @property
+    def get_content(self):
+        return {
+            "trading_pair": "MX-USDT",
+            "update_id": 1637654307737,
+            "bids": [{"price": "2.7548", "quantity": "28.18"}],
+            "asks": [{"price": "2.7348", "quantity": "18.18"}]
+        }
+
+    def test_equality_based_on_type_and_timestamp(self):
+        message = MexcOrderBookMessage(message_type=OrderBookMessageType.SNAPSHOT,
+                                       content={"data": []},
+                                       timestamp=10000000)
+        equal_message = MexcOrderBookMessage(message_type=OrderBookMessageType.SNAPSHOT,
+                                             content={"data": []},
+                                             timestamp=10000000)
+        message_with_different_type = MexcOrderBookMessage(message_type=OrderBookMessageType.DIFF,
+                                                           content={"data": []},
+                                                           timestamp=10000000)
+        message_with_different_timestamp = MexcOrderBookMessage(message_type=OrderBookMessageType.SNAPSHOT,
+                                                                content={"data": []},
+                                                                timestamp=90000000)
+
+        self.assertEqual(message, message)
+        self.assertEqual(message, equal_message)
+        self.assertNotEqual(message, message_with_different_type)
+        self.assertNotEqual(message, message_with_different_timestamp)
+
+    def test_equal_messages_have_equal_hash(self):
+        message = MexcOrderBookMessage(message_type=OrderBookMessageType.SNAPSHOT,
+                                       content={"data": []},
+                                       timestamp=10000000)
+        equal_message = MexcOrderBookMessage(message_type=OrderBookMessageType.SNAPSHOT,
+                                             content={"data": []},
+                                             timestamp=10000000)
+
+        self.assertEqual(hash(message), hash(equal_message))
+
+    def test_delete_buy_order_book_entry_always_has_zero_amount(self):
+        message = MexcOrderBookMessage(message_type=OrderBookMessageType.DIFF,
+                                       content=self.get_content,
+                                       timestamp=1637654307737)
+        bids = message.bids
+
+        self.assertEqual(1, len(bids))
+        self.assertEqual(2.7548, bids[0].price)
+        self.assertEqual(28.18, bids[0].amount)
+        self.assertEqual(1637654307737000, bids[0].update_id)
+
+    def test_delete_sell_order_book_entry_always_has_zero_amount(self):
+        message = MexcOrderBookMessage(message_type=OrderBookMessageType.DIFF,
+                                       content=self.get_content,
+                                       timestamp=1637654307737)
+        asks = message.asks
+
+        self.assertEqual(1, len(asks))
+        self.assertEqual(2.7348, asks[0].price)
+        self.assertEqual(18.18, asks[0].amount)
+        self.assertEqual(1637654307737000, asks[0].update_id)
diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_order_book_tracker.py b/test/hummingbot/connector/exchange/mexc/test_mexc_order_book_tracker.py
new file mode 100644
index 00000000000..d4b429a7b12
--- /dev/null
+++ b/test/hummingbot/connector/exchange/mexc/test_mexc_order_book_tracker.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+import unittest
+import asyncio
+from decimal import Decimal
+from typing import Any
+from unittest.mock import patch, AsyncMock
+
+import hummingbot.connector.exchange.mexc.mexc_constants as CONSTANTS
+
+from hummingbot.core.data_type.order_book import OrderBook
+
+from hummingbot.connector.exchange.mexc.mexc_order_book import MexcOrderBook
+from hummingbot.connector.exchange.mexc.mexc_order_book_message import MexcOrderBookMessage
+from hummingbot.connector.exchange.mexc.mexc_order_book_tracker import MexcOrderBookTracker
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
+
+
+class MexcOrderBookTrackerUnitTest(unittest.TestCase):
+
+    @property
+    def content(self):
+        return {"asks": [{"price": "37751.0", "quantity": "0.015"}],
+                "bids": [{"price": "37750.0", "quantity": "0.015"}]}
+
+    @property
+    def mock_data(self):
+        _data = {"code": 200, "data": {
+            "asks": [{"price": "56454.0", "quantity": "0.799072"}, {"price": "56455.28", "quantity": "0.008663"}],
+            "bids": [{"price": "56451.0", "quantity": "0.008663"}, {"price": "56449.99", "quantity": "0.173078"}],
+            "version": "547878563"}}
+        return _data
+
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        cls.base_asset = "COINALPHA"
+        cls.quote_asset = "HBOT"
+        cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}"
+        cls.instrument_id = 1
+
+        cls.ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
+
+    def setUp(self) -> None:
+        super().setUp()
+        throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS)
+        self.tracker: MexcOrderBookTracker = MexcOrderBookTracker(throttler=throttler,
+                                                                  trading_pairs=[self.trading_pair])
+        self.tracking_task = None
+
+        # Simulate start()
+        self.tracker._order_books[self.trading_pair] = MexcOrderBook()
+        self.tracker._tracking_message_queues[self.trading_pair] = asyncio.Queue()
+        self.tracker._order_books_initialized.set()
+
+    def tearDown(self) -> None:
+        self.tracking_task and self.tracking_task.cancel()
+        if len(self.tracker._tracking_tasks) > 0:
+            for task in self.tracker._tracking_tasks.values():
+                task.cancel()
+        super().tearDown()
+
+    @staticmethod
+    def set_mock_response(mock_api, status: int, json_data: Any):
+        mock_api.return_value.__aenter__.return_value.status = status
+        mock_api.return_value.__aenter__.return_value.json = AsyncMock(return_value=json_data)
+
+    def simulate_queue_order_book_messages(self, message: MexcOrderBookMessage):
+        message_queue = self.tracker._tracking_message_queues[self.trading_pair]
+        message_queue.put_nowait(message)
+
+    def test_exchange_name(self):
+        self.assertEqual(self.tracker.exchange_name, CONSTANTS.EXCHANGE_NAME)
+
+    def test_track_single_book_apply_snapshot(self):
+        snapshot_msg = MexcOrderBook.snapshot_message_from_exchange(
+            msg=self.content,
+            timestamp=1626788175000,
+            trading_pair=self.trading_pair
+        )
+        self.simulate_queue_order_book_messages(snapshot_msg)
+
+        with self.assertRaises(asyncio.TimeoutError):
+            # Allow 5 seconds for tracker to process some messages.
+            self.tracking_task = self.ev_loop.create_task(asyncio.wait_for(
+                self.tracker._track_single_book(self.trading_pair),
+                2.0
+            ))
+            self.ev_loop.run_until_complete(self.tracking_task)
+
+        self.assertEqual(1626788175000000, self.tracker.order_books[self.trading_pair].snapshot_uid)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_init_order_books(self, mock_api):
+        self.set_mock_response(mock_api, 200, self.mock_data)
+
+        self.tracker._order_books_initialized.clear()
+        self.tracker._tracking_message_queues.clear()
+        self.tracker._tracking_tasks.clear()
+        self.tracker._order_books.clear()
+
+        self.assertEqual(0, len(self.tracker.order_books))
+        self.assertEqual(0, len(self.tracker._tracking_message_queues))
+        self.assertEqual(0, len(self.tracker._tracking_tasks))
+        self.assertFalse(self.tracker._order_books_initialized.is_set())
+
+        init_order_books_task = self.ev_loop.create_task(
+            self.tracker._init_order_books()
+        )
+
+        self.ev_loop.run_until_complete(init_order_books_task)
+
+        self.assertIsInstance(self.tracker.order_books[self.trading_pair], OrderBook)
+        self.assertTrue(self.tracker._order_books_initialized.is_set())
+
+    @patch("aiohttp.ClientSession.get")
+    def test_can_get_price_after_order_book_init(self, mock_api):
+        self.set_mock_response(mock_api, 200, self.mock_data)
+
+        init_order_books_task = self.ev_loop.create_task(
+            self.tracker._init_order_books()
+        )
+        self.ev_loop.run_until_complete(init_order_books_task)
+
+        ob = self.tracker.order_books[self.trading_pair]
+        ask_price = ob.get_price(True)
+        self.assertEqual(Decimal("56454.0"), ask_price)
diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_user_stream_tracker.py b/test/hummingbot/connector/exchange/mexc/test_mexc_user_stream_tracker.py
new file mode 100644
index 00000000000..9048857cf1d
--- /dev/null
+++ b/test/hummingbot/connector/exchange/mexc/test_mexc_user_stream_tracker.py
@@ -0,0 +1,44 @@
+import asyncio
+from unittest import TestCase
+from unittest.mock import AsyncMock, patch
+
+import ujson
+
+from hummingbot.connector.exchange.mexc.mexc_auth import MexcAuth
+import hummingbot.connector.exchange.mexc.mexc_constants as CONSTANTS
+from hummingbot.connector.exchange.mexc.mexc_user_stream_tracker import MexcUserStreamTracker
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
+from test.hummingbot.connector.network_mocking_assistant import NetworkMockingAssistant
+
+
+class MexcUserStreamTrackerTests(TestCase):
+
+    def setUp(self) -> None:
+        super().setUp()
+        self.ws_sent_messages = []
+        self.ws_incoming_messages = asyncio.Queue()
+        self.listening_task = None
+
+        throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS)
+        auth_assistant = MexcAuth(api_key='testAPIKey',
+                                  secret_key='testSecret', )
+        self.tracker = MexcUserStreamTracker(throttler=throttler, mexc_auth=auth_assistant)
+
+        self.mocking_assistant = NetworkMockingAssistant()
+
+    def tearDown(self) -> None:
+        self.listening_task and self.listening_task.cancel()
+        super().tearDown()
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_listening_process_authenticates_and_subscribes_to_events(self, ws_connect_mock):
+        ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock()
+
+        self.listening_task = asyncio.get_event_loop().create_task(
+            self.tracker.start())
+        self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value,
+                                                             ujson.dumps({'channel': 'push.personal.order'}))
+
+        first_received_message = asyncio.get_event_loop().run_until_complete(self.tracker.user_stream.get())
+
+        self.assertEqual({'channel': 'push.personal.order'}, first_received_message)
diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_websocket_adaptor.py b/test/hummingbot/connector/exchange/mexc/test_mexc_websocket_adaptor.py
new file mode 100644
index 00000000000..990804e091c
--- /dev/null
+++ b/test/hummingbot/connector/exchange/mexc/test_mexc_websocket_adaptor.py
@@ -0,0 +1,109 @@
+import asyncio
+import unittest
+
+from typing import Awaitable, Optional
+from unittest.mock import AsyncMock, patch
+
+from hummingbot.connector.exchange.mexc.mexc_auth import MexcAuth
+from hummingbot.connector.exchange.mexc.mexc_websocket_adaptor import MexcWebSocketAdaptor
+from test.hummingbot.connector.network_mocking_assistant import NetworkMockingAssistant
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
+import hummingbot.connector.exchange.mexc.mexc_constants as CONSTANTS
+
+
+class MexcWebSocketUnitTests(unittest.TestCase):
+    # the level is required to receive logs from the data source logger
+    level = 0
+
+    @classmethod
+    def setUpClass(cls) -> None:
+        super().setUpClass()
+        cls.ev_loop = asyncio.get_event_loop()
+        cls.trading_pairs = ["COINALPHA-HBOT"]
+
+        cls.api_key = "someKey"
+        cls.secret_key = "someSecretKey"
+        cls.auth = MexcAuth(api_key=cls.api_key, secret_key=cls.secret_key)
+
+    def setUp(self) -> None:
+        super().setUp()
+        self.log_records = []
+        throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS)
+
+        self.websocket = MexcWebSocketAdaptor(throttler)
+        self.websocket.logger().setLevel(1)
+        self.websocket.logger().addHandler(self)
+
+        self.mocking_assistant = NetworkMockingAssistant()
+        self.async_task: Optional[asyncio.Task] = None
+
+        self.resume_test_event = asyncio.Event()
+
+    def tearDown(self) -> None:
+        self.ev_loop.run_until_complete(self.websocket.disconnect())
+        self.async_task and self.async_task.cancel()
+        super().tearDown()
+
+    def handle(self, record):
+        self.log_records.append(record)
+
+    def _is_logged(self, log_level: str, message: str) -> bool:
+        return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records)
+
+    def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1):
+        ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout))
+        return ret
+
+    def resume_test_callback(self):
+        self.resume_test_event.set()
+
+    async def _iter_message(self):
+        async for _ in self.websocket.iter_messages():
+            self.resume_test_callback()
+            self.async_task.cancel()
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_connect_raises_exception(self, ws_connect_mock):
+        throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS)
+        ws_connect_mock.side_effect = Exception("TEST ERROR")
+
+        self.websocket = MexcWebSocketAdaptor(throttler)
+
+        with self.assertRaisesRegex(Exception, "TEST ERROR"):
+            self.async_run_with_timeout(self.websocket.connect())
+
+        self.assertTrue(self._is_logged("ERROR", "Websocket error: 'TEST ERROR'"))
+
+    def test_disconnect(self):
+        ws = AsyncMock()
+        self.websocket._websocket = ws
+
+        self.async_run_with_timeout(self.websocket.disconnect())
+
+        self.assertEqual(1, ws.close.await_count)
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_subscribe_to_order_book_streams_raises_cancelled_exception(self, ws_connect_mock):
+        ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock()
+
+        self.async_run_with_timeout(self.websocket.connect())
+
+        ws_connect_mock.return_value.send_str.side_effect = asyncio.CancelledError
+
+        with self.assertRaises(asyncio.CancelledError):
+            self.async_run_with_timeout(self.websocket.subscribe_to_order_book_streams(self.trading_pairs))
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_subscribe_to_order_book_streams_logs_exception(self, ws_connect_mock):
+        ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock()
+
+        self.async_run_with_timeout(self.websocket.connect())
+
+        ws_connect_mock.return_value.send_str.side_effect = Exception("TEST ERROR")
+
+        with self.assertRaisesRegex(Exception, "TEST ERROR"):
+            self.async_run_with_timeout(self.websocket.subscribe_to_order_book_streams(self.trading_pairs))
+
+        self.assertTrue(self._is_logged(
+            "ERROR", "Unexpected error occurred subscribing to order book trading and delta streams..."
+        ))


Code review comments:
diff_hunk: @@ -0,0 +1,1171 @@
+import asyncio
+import time
+import pandas as pd
+
+import ujson
+from decimal import Decimal
+from typing import Any, Dict, List
+from unittest import TestCase
+from unittest.mock import AsyncMock, PropertyMock, patch
+
+from hummingbot.connector.exchange.mexc.mexc_exchange import MexcExchange
+from hummingbot.connector.exchange.mexc.mexc_in_flight_order import (
+    MexcInFlightOrder
+)
+from hummingbot.connector.exchange.mexc.mexc_order_book import MexcOrderBook
+from hummingbot.connector.trading_rule import TradingRule
+from hummingbot.core.event.events import (
+    OrderCancelledEvent,
+    OrderType,
+    TradeType, SellOrderCompletedEvent,
+)
+from hummingbot.core.network_iterator import NetworkStatus
+from hummingbot.core.utils.async_utils import safe_ensure_future
+from test.hummingbot.connector.network_mocking_assistant import NetworkMockingAssistant
+
+
+class MexcExchangeTests(TestCase):
+    # the level is required to receive logs from the data source loger
+    level = 0
+
+    start_timestamp: float = pd.Timestamp("2021-01-01", tz="UTC").timestamp()
+
+    @classmethod
+    def setUpClass(cls) -> None:
+        super().setUpClass()
+        cls.base_asset = "MX"
+        cls.quote_asset = "USDT"
+        cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}"
+
+    def setUp(self) -> None:
+        super().setUp()
+
+        self.tracker_task = None
+        self.exchange_task = None
+        self.log_records = []
+        self.resume_test_event = asyncio.Event()
+        self._account_name = "hbot"
+
+        self.exchange = MexcExchange(mexc_api_key='testAPIKey',
+                                     mexc_secret_key='testSecret',
+                                     trading_pairs=[self.trading_pair])
+
+        self.exchange.logger().setLevel(1)
+        self.exchange.logger().addHandler(self)
+        self.exchange._account_id = 1
+
+        self.mocking_assistant = NetworkMockingAssistant()
+        self.mock_done_event = asyncio.Event()
+
+    def tearDown(self) -> None:
+        self.tracker_task and self.tracker_task.cancel()
+        self.exchange_task and self.exchange_task.cancel()
+        super().tearDown()
+
+    def handle(self, record):
+        self.log_records.append(record)
+
+    def _is_logged(self, log_level: str, message: str) -> bool:
+        return any(record.levelname == log_level and record.getMessage() == message
+                   for record in self.log_records)
+
+    def _create_exception_and_unlock_test_with_event(self, exception):
+        self.resume_test_event.set()
+        raise exception
+
+    def _mock_responses_done_callback(self, *_, **__):
+        self.mock_done_event.set()
+
+    def _simulate_reset_poll_notifier(self):
+        self.exchange._poll_notifier.clear()
+
+    def _simulate_ws_message_received(self, timestamp: float):
+        self.exchange._user_stream_tracker._data_source._last_recv_time = timestamp
+
+    def _simulate_trading_rules_initialized(self):
+        self.exchange._trading_rules = {
+            self.trading_pair: TradingRule(
+                trading_pair=self.trading_pair,
+                min_order_size=4,
+                min_price_increment=Decimal(str(0.0001)),
+                min_base_amount_increment=2,
+                min_notional_size=Decimal(str(5))
+            )
+        }
+
+    @property
+    def order_book_data(self):
+        _data = {"code": 200, "data": {
+            "asks": [{"price": "56454.0", "quantity": "0.799072"}, {"price": "56455.28", "quantity": "0.008663"}],
+            "bids": [{"price": "56451.0", "quantity": "0.008663"}, {"price": "56449.99", "quantity": "0.173078"}],
+            "version": "547878563"}}
+        return _data
+
+    def _simulate_create_order(self,
+                               trade_type: TradeType,
+                               order_id: str,
+                               trading_pair: str,
+                               amount: Decimal,
+                               price: Decimal = Decimal("0"),
+                               order_type: OrderType = OrderType.MARKET):
+        future = safe_ensure_future(
+            self.exchange.execute_buy(order_id, trading_pair, amount, order_type, price)
+        )
+        self.exchange.start_tracking_order(
+            order_id, None, self.trading_pair, TradeType.BUY, Decimal(10.0), Decimal(1.0), OrderType.LIMIT
+        )
+        return future
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_user_event_queue_error_is_logged(self, ws_connect_mock):
+        ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock()
+
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+
+        dummy_user_stream = AsyncMock()
+        dummy_user_stream.get.side_effect = lambda: self._create_exception_and_unlock_test_with_event(
+            Exception("Dummy test error"))
+        self.exchange._user_stream_tracker._user_stream = dummy_user_stream
+
+        # Add a dummy message for the websocket to read and include in the "messages" queue
+        self.mocking_assistant.add_websocket_text_message(ws_connect_mock,
+                                                          ujson.dumps({'channel': 'push.personal.order'}))
+        asyncio.get_event_loop().run_until_complete(self.resume_test_event.wait())
+        self.resume_test_event.clear()
+
+        try:
+            self.exchange_task.cancel()
+            asyncio.get_event_loop().run_until_complete(self.exchange_task)
+        except asyncio.CancelledError:
+            pass
+        except Exception:
+            pass
+
+        self.assertTrue(self._is_logged('ERROR', "Unknown error. Retrying after 1 second. Dummy test error"))
+
+    def test_user_event_queue_notifies_cancellations(self):
+        self.tracker_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+
+        dummy_user_stream = AsyncMock()
+        dummy_user_stream.get.side_effect = lambda: self._create_exception_and_unlock_test_with_event(
+            asyncio.CancelledError())
+        self.exchange._user_stream_tracker._user_stream = dummy_user_stream
+
+        # Yield processor for the exchange to process the new message
+        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.3))
+
+        with self.assertRaises(asyncio.CancelledError):
+            asyncio.get_event_loop().run_until_complete(self.tracker_task)
+
+    def test_exchange_logs_unknown_event_message(self):
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+        payload = {'channel': 'test'}
+        self.exchange._user_stream_tracker.user_stream.put_nowait(payload)
+
+        # Yield processor for the exchange to process the new message
+        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.3))
path: test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py
text: @user1:
You can use the same approach as in the previous test to eliminate the waiting here and the remainder of tests using this approach to testing.

diff_hunk: @@ -0,0 +1,216 @@
+import unittest
+import asyncio
+from collections import deque
+
+import ujson
+
+import hummingbot.connector.exchange.mexc.mexc_constants as CONSTANTS
+
+from unittest.mock import patch, AsyncMock
+from typing import (
+    Any,
+    Dict,
+)
+
+from hummingbot.connector.exchange.mexc.mexc_api_order_book_data_source import MexcAPIOrderBookDataSource
+
+from hummingbot.core.data_type.order_book import OrderBook
+
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
+from hummingbot.core.data_type.order_book_message import OrderBookMessageType
+from hummingbot.core.utils.async_utils import safe_ensure_future
+from test.hummingbot.connector.network_mocking_assistant import NetworkMockingAssistant
+
+
+class MexcAPIOrderBookDataSourceUnitTests(unittest.TestCase):
+    # logging.Level required to receive logs from the data source logger
+    level = 0
+
+    @classmethod
+    def setUpClass(cls) -> None:
+        super().setUpClass()
+
+        cls.ev_loop = asyncio.get_event_loop()
+        cls.base_asset = "BTC"
+        cls.quote_asset = "USDT"
+        cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}"
+        cls.instrument_id = 1
+
+    def setUp(self) -> None:
+        super().setUp()
+        self.log_records = []
+        self.listening_task = None
+
+        self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS)
+        self.data_source = MexcAPIOrderBookDataSource(throttler=self.throttler, trading_pairs=[self.trading_pair])
+        self.data_source.logger().setLevel(1)
+        self.data_source.logger().addHandler(self)
+
+        self.mocking_assistant = NetworkMockingAssistant()
+
+    def tearDown(self) -> None:
+        self.listening_task and self.listening_task.cancel()
+        super().tearDown()
+
+    def handle(self, record):
+        self.log_records.append(record)
+
+    def _raise_exception(self, exception_class):
+        raise exception_class
+
+    def _is_logged(self, log_level: str, message: str) -> bool:
+        return any(record.levelname == log_level and record.getMessage() == message
+                   for record in self.log_records)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_last_traded_prices(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        mock_response: Dict[Any] = {"code": 200, "data": [
+            {"symbol": "BTC_USDT", "volume": "1076.002782", "high": "59387.98", "low": "57009", "bid": "57920.98",
+             "ask": "57921.03", "open": "57735.92", "last": "57902.52", "time": 1637898900000,
+             "change_rate": "0.00288555"}]}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response)
+
+        results = self.ev_loop.run_until_complete(
+            asyncio.gather(self.data_source.get_last_traded_prices([self.trading_pair])))
+        results: Dict[str, Any] = results[0]
+
+        self.assertEqual(results[self.trading_pair], 57902.52)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_fetch_trading_pairs_with_error_status_in_response(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        mock_response = {}
+        self.mocking_assistant.add_http_response(mock_api, 100, mock_response)
+
+        result = self.ev_loop.run_until_complete(self.data_source.fetch_trading_pairs())
+        self.assertEqual(0, len(result))
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_order_book_data(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        mock_response = {"code": 200, "data": {"asks": [{"price": "57974.06", "quantity": "0.247421"}],
+                                               "bids": [{"price": "57974.01", "quantity": "0.201635"}],
+                                               "version": "562370278"}}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response)
+
+        results = self.ev_loop.run_until_complete(
+            asyncio.gather(self.data_source.get_snapshot(self.data_source._shared_client, self.trading_pair)))
+        result = results[0]
+
+        self.assertTrue("asks" in result)
+        self.assertGreaterEqual(len(result), 0)
+        self.assertEqual(mock_response.get("data"), result)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_order_book_data_raises_exception_when_response_has_error_code(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+
+        mock_response = {"Erroneous response"}
+        self.mocking_assistant.add_http_response(mock_api, 100, mock_response)
+
+        with self.assertRaises(IOError) as context:
+            self.ev_loop.run_until_complete(self.data_source.get_snapshot(self.data_source._shared_client,
+                                                                          self.trading_pair))
+
+        self.assertEqual(str(context.exception),
+                         f'Error fetching MEXC market snapshot for {self.trading_pair.replace("-", "_")}. '
+                         f'HTTP status is {100}.')
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_new_order_book(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+
+        mock_response = {"code": 200, "data": {"asks": [{"price": "57974.06", "quantity": "0.247421"}],
+                                               "bids": [{"price": "57974.01", "quantity": "0.201635"}],
+                                               "version": "562370278"}}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response)
+
+        results = self.ev_loop.run_until_complete(
+            asyncio.gather(self.data_source.get_new_order_book(self.trading_pair)))
+        result: OrderBook = results[0]
+
+        self.assertTrue(type(result) == OrderBook)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_listen_for_snapshots_cancelled_when_fetching_snapshot(self, mock_api):
+        mock_api.side_effect = asyncio.CancelledError
+
+        msg_queue: asyncio.Queue = asyncio.Queue()
+        with self.assertRaises(asyncio.CancelledError):
+            self.listening_task = self.ev_loop.create_task(
+                self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)
+            )
+            self.ev_loop.run_until_complete(self.listening_task)
+
+        self.assertEqual(msg_queue.qsize(), 0)
+
+    @patch("asyncio.sleep")
path: test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py
text: @user1:
`asyncio.sleep` should not be patched directly as this could affect other portions of the code also using this function (other than the one intended to be patched). To avoid this, the object that calls `asyncio.sleep` can have a member function called `_sleep` that it then calls `asyncio.sleep`. Then, the mock will be on the member `_sleep` function.

This goes for any other tests patching `asyncio.sleep` directly.

diff_hunk: @@ -0,0 +1,1171 @@
+import asyncio
+import time
+import pandas as pd
+
+import ujson
+from decimal import Decimal
+from typing import Any, Dict, List
+from unittest import TestCase
+from unittest.mock import AsyncMock, PropertyMock, patch
+
+from hummingbot.connector.exchange.mexc.mexc_exchange import MexcExchange
+from hummingbot.connector.exchange.mexc.mexc_in_flight_order import (
+    MexcInFlightOrder
+)
+from hummingbot.connector.exchange.mexc.mexc_order_book import MexcOrderBook
+from hummingbot.connector.trading_rule import TradingRule
+from hummingbot.core.event.events import (
+    OrderCancelledEvent,
+    OrderType,
+    TradeType, SellOrderCompletedEvent,
+)
+from hummingbot.core.network_iterator import NetworkStatus
+from hummingbot.core.utils.async_utils import safe_ensure_future
+from test.hummingbot.connector.network_mocking_assistant import NetworkMockingAssistant
+
+
+class MexcExchangeTests(TestCase):
+    # the level is required to receive logs from the data source loger
+    level = 0
+
+    start_timestamp: float = pd.Timestamp("2021-01-01", tz="UTC").timestamp()
+
+    @classmethod
+    def setUpClass(cls) -> None:
+        super().setUpClass()
+        cls.base_asset = "MX"
+        cls.quote_asset = "USDT"
+        cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}"
+
+    def setUp(self) -> None:
+        super().setUp()
+
+        self.tracker_task = None
+        self.exchange_task = None
+        self.log_records = []
+        self.resume_test_event = asyncio.Event()
+        self._account_name = "hbot"
+
+        self.exchange = MexcExchange(mexc_api_key='testAPIKey',
+                                     mexc_secret_key='testSecret',
+                                     trading_pairs=[self.trading_pair])
+
+        self.exchange.logger().setLevel(1)
+        self.exchange.logger().addHandler(self)
+        self.exchange._account_id = 1
+
+        self.mocking_assistant = NetworkMockingAssistant()
+        self.mock_done_event = asyncio.Event()
+
+    def tearDown(self) -> None:
+        self.tracker_task and self.tracker_task.cancel()
+        self.exchange_task and self.exchange_task.cancel()
+        super().tearDown()
+
+    def handle(self, record):
+        self.log_records.append(record)
+
+    def _is_logged(self, log_level: str, message: str) -> bool:
+        return any(record.levelname == log_level and record.getMessage() == message
+                   for record in self.log_records)
+
+    def _create_exception_and_unlock_test_with_event(self, exception):
+        self.resume_test_event.set()
+        raise exception
+
+    def _mock_responses_done_callback(self, *_, **__):
+        self.mock_done_event.set()
+
+    def _simulate_reset_poll_notifier(self):
+        self.exchange._poll_notifier.clear()
+
+    def _simulate_ws_message_received(self, timestamp: float):
+        self.exchange._user_stream_tracker._data_source._last_recv_time = timestamp
+
+    def _simulate_trading_rules_initialized(self):
+        self.exchange._trading_rules = {
+            self.trading_pair: TradingRule(
+                trading_pair=self.trading_pair,
+                min_order_size=4,
+                min_price_increment=Decimal(str(0.0001)),
+                min_base_amount_increment=2,
+                min_notional_size=Decimal(str(5))
+            )
+        }
+
+    @property
+    def order_book_data(self):
+        _data = {"code": 200, "data": {
+            "asks": [{"price": "56454.0", "quantity": "0.799072"}, {"price": "56455.28", "quantity": "0.008663"}],
+            "bids": [{"price": "56451.0", "quantity": "0.008663"}, {"price": "56449.99", "quantity": "0.173078"}],
+            "version": "547878563"}}
+        return _data
+
+    def _simulate_create_order(self,
+                               trade_type: TradeType,
+                               order_id: str,
+                               trading_pair: str,
+                               amount: Decimal,
+                               price: Decimal = Decimal("0"),
+                               order_type: OrderType = OrderType.MARKET):
+        future = safe_ensure_future(
+            self.exchange.execute_buy(order_id, trading_pair, amount, order_type, price)
+        )
+        self.exchange.start_tracking_order(
+            order_id, None, self.trading_pair, TradeType.BUY, Decimal(10.0), Decimal(1.0), OrderType.LIMIT
+        )
+        return future
+
+    @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock)
+    def test_user_event_queue_error_is_logged(self, ws_connect_mock):
+        ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock()
+
+        self.exchange_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+
+        dummy_user_stream = AsyncMock()
+        dummy_user_stream.get.side_effect = lambda: self._create_exception_and_unlock_test_with_event(
+            Exception("Dummy test error"))
+        self.exchange._user_stream_tracker._user_stream = dummy_user_stream
+
+        # Add a dummy message for the websocket to read and include in the "messages" queue
+        self.mocking_assistant.add_websocket_text_message(ws_connect_mock,
+                                                          ujson.dumps({'channel': 'push.personal.order'}))
+        asyncio.get_event_loop().run_until_complete(self.resume_test_event.wait())
+        self.resume_test_event.clear()
+
+        try:
+            self.exchange_task.cancel()
+            asyncio.get_event_loop().run_until_complete(self.exchange_task)
+        except asyncio.CancelledError:
+            pass
+        except Exception:
+            pass
+
+        self.assertTrue(self._is_logged('ERROR', "Unknown error. Retrying after 1 second. Dummy test error"))
+
+    def test_user_event_queue_notifies_cancellations(self):
+        self.tracker_task = asyncio.get_event_loop().create_task(
+            self.exchange._user_stream_event_listener())
+
+        dummy_user_stream = AsyncMock()
+        dummy_user_stream.get.side_effect = lambda: self._create_exception_and_unlock_test_with_event(
+            asyncio.CancelledError())
+        self.exchange._user_stream_tracker._user_stream = dummy_user_stream
+
+        # Yield processor for the exchange to process the new message
+        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.3))
path: test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py
text: @user1:
This directly adds `0.3` seconds to the test suite, and we are actively trying to identify those tests that use this strategy in our codebase and refactor them. The way to avoid waiting for a fixed amount of time is to instead await `self.resume_test_event`, which will be set by the `side_effect` defined above.

diff_hunk: @@ -0,0 +1,216 @@
+import unittest
+import asyncio
+from collections import deque
+
+import ujson
+
+import hummingbot.connector.exchange.mexc.mexc_constants as CONSTANTS
+
+from unittest.mock import patch, AsyncMock
+from typing import (
+    Any,
+    Dict,
+)
+
+from hummingbot.connector.exchange.mexc.mexc_api_order_book_data_source import MexcAPIOrderBookDataSource
+
+from hummingbot.core.data_type.order_book import OrderBook
+
+from hummingbot.core.api_throttler.async_throttler import AsyncThrottler
+from hummingbot.core.data_type.order_book_message import OrderBookMessageType
+from hummingbot.core.utils.async_utils import safe_ensure_future
+from test.hummingbot.connector.network_mocking_assistant import NetworkMockingAssistant
+
+
+class MexcAPIOrderBookDataSourceUnitTests(unittest.TestCase):
+    # logging.Level required to receive logs from the data source logger
+    level = 0
+
+    @classmethod
+    def setUpClass(cls) -> None:
+        super().setUpClass()
+
+        cls.ev_loop = asyncio.get_event_loop()
+        cls.base_asset = "BTC"
+        cls.quote_asset = "USDT"
+        cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}"
+        cls.instrument_id = 1
+
+    def setUp(self) -> None:
+        super().setUp()
+        self.log_records = []
+        self.listening_task = None
+
+        self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS)
+        self.data_source = MexcAPIOrderBookDataSource(throttler=self.throttler, trading_pairs=[self.trading_pair])
+        self.data_source.logger().setLevel(1)
+        self.data_source.logger().addHandler(self)
+
+        self.mocking_assistant = NetworkMockingAssistant()
+
+    def tearDown(self) -> None:
+        self.listening_task and self.listening_task.cancel()
+        super().tearDown()
+
+    def handle(self, record):
+        self.log_records.append(record)
+
+    def _raise_exception(self, exception_class):
+        raise exception_class
+
+    def _is_logged(self, log_level: str, message: str) -> bool:
+        return any(record.levelname == log_level and record.getMessage() == message
+                   for record in self.log_records)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_last_traded_prices(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        mock_response: Dict[Any] = {"code": 200, "data": [
+            {"symbol": "BTC_USDT", "volume": "1076.002782", "high": "59387.98", "low": "57009", "bid": "57920.98",
+             "ask": "57921.03", "open": "57735.92", "last": "57902.52", "time": 1637898900000,
+             "change_rate": "0.00288555"}]}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response)
+
+        results = self.ev_loop.run_until_complete(
+            asyncio.gather(self.data_source.get_last_traded_prices([self.trading_pair])))
+        results: Dict[str, Any] = results[0]
+
+        self.assertEqual(results[self.trading_pair], 57902.52)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_fetch_trading_pairs_with_error_status_in_response(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        mock_response = {}
+        self.mocking_assistant.add_http_response(mock_api, 100, mock_response)
+
+        result = self.ev_loop.run_until_complete(self.data_source.fetch_trading_pairs())
+        self.assertEqual(0, len(result))
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_order_book_data(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+        mock_response = {"code": 200, "data": {"asks": [{"price": "57974.06", "quantity": "0.247421"}],
+                                               "bids": [{"price": "57974.01", "quantity": "0.201635"}],
+                                               "version": "562370278"}}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response)
+
+        results = self.ev_loop.run_until_complete(
+            asyncio.gather(self.data_source.get_snapshot(self.data_source._shared_client, self.trading_pair)))
+        result = results[0]
+
+        self.assertTrue("asks" in result)
+        self.assertGreaterEqual(len(result), 0)
+        self.assertEqual(mock_response.get("data"), result)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_order_book_data_raises_exception_when_response_has_error_code(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+
+        mock_response = {"Erroneous response"}
+        self.mocking_assistant.add_http_response(mock_api, 100, mock_response)
+
+        with self.assertRaises(IOError) as context:
+            self.ev_loop.run_until_complete(self.data_source.get_snapshot(self.data_source._shared_client,
+                                                                          self.trading_pair))
+
+        self.assertEqual(str(context.exception),
+                         f'Error fetching MEXC market snapshot for {self.trading_pair.replace("-", "_")}. '
+                         f'HTTP status is {100}.')
+
+    @patch("aiohttp.ClientSession.get")
+    def test_get_new_order_book(self, mock_api):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+
+        mock_response = {"code": 200, "data": {"asks": [{"price": "57974.06", "quantity": "0.247421"}],
+                                               "bids": [{"price": "57974.01", "quantity": "0.201635"}],
+                                               "version": "562370278"}}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response)
+
+        results = self.ev_loop.run_until_complete(
+            asyncio.gather(self.data_source.get_new_order_book(self.trading_pair)))
+        result: OrderBook = results[0]
+
+        self.assertTrue(type(result) == OrderBook)
+
+    @patch("aiohttp.ClientSession.get")
+    def test_listen_for_snapshots_cancelled_when_fetching_snapshot(self, mock_api):
+        mock_api.side_effect = asyncio.CancelledError
+
+        msg_queue: asyncio.Queue = asyncio.Queue()
+        with self.assertRaises(asyncio.CancelledError):
+            self.listening_task = self.ev_loop.create_task(
+                self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)
+            )
+            self.ev_loop.run_until_complete(self.listening_task)
+
+        self.assertEqual(msg_queue.qsize(), 0)
+
+    @patch("asyncio.sleep")
+    @patch("aiohttp.ClientSession.get")
+    def test_listen_for_snapshots_successful(self, mock_api, mock_sleep):
+        self.mocking_assistant.configure_http_request_mock(mock_api)
+
+        # the queue and the division by zero error are used just to synchronize the test
+        sync_queue = deque()
+        sync_queue.append(1)
+
+        mock_response = {"code": 200, "data": {"asks": [{"price": "57974.06", "quantity": "0.247421"}],
+                                               "bids": [{"price": "57974.01", "quantity": "0.201635"}],
+                                               "version": "562370278"}}
+        self.mocking_assistant.add_http_response(mock_api, 200, mock_response)
+
+        mock_sleep.side_effect = lambda delay: 1 / 0 if len(sync_queue) == 0 else sync_queue.pop()
+
+        msg_queue: asyncio.Queue = asyncio.Queue()
+        with self.assertRaises(ZeroDivisionError):
+            self.listening_task = self.ev_loop.create_task(
+                self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue))
+            self.ev_loop.run_until_complete(self.listening_task)
path: test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py
text: @user1:
Instead of using `self.ev_loop.run_until_complete`, which can result in a failed test case awaiting indefinitely, please add this function to the test class and use it instead:
```Python
    def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1):
        ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout))
        return ret
```
You can grep for it in the tests folder to see examples.

This goes for all tests using `run_until_complete` on the method being tested.