Repository: hummingbot/hummingbot
Issue: Binance Perpetual - Persistent "Order's position side does not match user's setting" error

**Describe the bug**
Unable to use perpetual market making on binance.

**Steps To Reproduce**
start the bot with binance perpetual market making.

**Release version**
0.42.0

**Attachments**

```
021-08-19 16:46:09,675 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated.
2021-08-19 16:46:10,532 - 1 - hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative - INFO - Leverage Successfully set to 1 for DOGE-BUSD.
2021-08-19 16:46:10,533 - 1 - hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking...
2021-08-19 16:46:10,691 - 1 - hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative - ERROR - Error fetching /fapi/v1/positionSide/dual
Traceback (most recent call last):
  File "/home/hummingbot/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py", line 1060, in request
    raise IOError(f"Error fetching data from {path}. HTTP status is {response.status}. "
OSError: Error fetching data from /fapi/v1/positionSide/dual. HTTP status is 400. Request Error: {'code': -4059, 'msg': 'No need to change position side.'}
2021-08-19 16:46:10,693 - 1 - hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative - WARNING - Error fetching data from /fapi/v1/positionSide/dual. HTTP status is 400. Request Error: {'code': -4059, 'msg': 'No n$
2021-08-19 16:46:10,693 - 1 - hummingbot.core.utils.async_utils - ERROR - Unhandled error in background task: Error fetching data from /fapi/v1/positionSide/dual. HTTP status is 400. Request Error: {'code': -4059, 'msg': 'No need to chan$
Traceback (most recent call last):
  File "/home/hummingbot/hummingbot/core/utils/async_utils.py", line 9, in safe_wrapper
    return await c
  File "/home/hummingbot/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py", line 1005, in _set_position_mode
    mode = await self.request(
  File "/home/hummingbot/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py", line 1073, in request
    raise e
  File "/home/hummingbot/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py", line 1060, in request
    raise IOError(f"Error fetching data from {path}. HTTP status is {response.status}. "
OSError: Error fetching data from /fapi/v1/positionSide/dual. HTTP status is 400. Request Error: {'code': -4059, 'msg': 'No need to change position side.'}
2021-08-19 16:46:11,887 - 1 - hummingbot.connector.derivative.binance_perpetual.binance_perpetual_order_book_tracker - INFO - Initialized order book for DOGE-BUSD. 1/1 completed.
2021-08-19 16:46:16,004 - 1 - hummingbot.strategy.perpetual_market_making.perpetual_market_making - INFO - (DOGE-BUSD) Creating 1 LIMIT bid orders at (Size, Price): ['4E+1 DOGE, 0.30591 BUSD'] to OPEN position.
2021-08-19 16:46:16,005 - 1 - hummingbot.strategy.perpetual_market_making.perpetual_market_making - INFO - (DOGE-BUSD) Creating 1  LIMIT ask orders at (Size, Price): ['4E+1 DOGE, 0.30621 BUSD'] to OPEN position.
2021-08-19 16:46:16,463 - 1 - hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative - ERROR - Error fetching /fapi/v1/order
Traceback (most recent call last):
  File "/home/hummingbot/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py", line 1060, in request
    raise IOError(f"Error fetching data from {path}. HTTP status is {response.status}. "
OSError: Error fetching data from /fapi/v1/order. HTTP status is 400. Request Error: {'code': -4061, 'msg': "Order's position side does not match user's setting."}
2021-08-19 16:46:16,464 - 1 - hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative - WARNING - Error fetching data from /fapi/v1/order. HTTP status is 400. Request Error: {'code': -4061, 'msg': "Order's position$
2021-08-19 16:46:16,464 - 1 - hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative - NETWORK - Error submitting order to Binance Perpetuals for 40 DOGE-BUSD 0.306210.
Traceback (most recent call last):
  File "/home/hummingbot/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py", line 276, in create_order
    order_result = await self.request(path="/fapi/v1/order",
  File "/home/hummingbot/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py", line 1073, in request
    raise e
  File "/home/hummingbot/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py", line 1060, in request
    raise IOError(f"Error fetching data from {path}. HTTP status is {response.status}. "
OSError: Error fetching data from /fapi/v1/order. HTTP status is 400. Request Error: {'code': -4061, 'msg': "Order's position side does not match user's setting."}
```

**Debug Infos**
No matter if in binance future account preference the "normal/unidirectionnal" or "hedge mode"

Have a nice day


Changed files:
hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py
test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_derivative.py

diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py
index b16aad265..2b32233bb 100644
--- a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py
+++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py
@@ -146,7 +146,8 @@ class BinancePerpetualDerivative(ExchangeBase, PerpetualTrading):
             "order_books_initialized": self._order_book_tracker.ready,
             "account_balance": len(self._account_balances) > 0 if self._trading_required else True,
             "trading_rule_initialized": len(self._trading_rules) > 0,
-            "funding_info": len(self._funding_info) > 0
+            "funding_info": len(self._funding_info) > 0,
+            "position_mode": self.position_mode,
         }
 
     @property
@@ -165,6 +166,7 @@ class BinancePerpetualDerivative(ExchangeBase, PerpetualTrading):
         self._trading_rules_polling_task = safe_ensure_future(self._trading_rules_polling_loop())
         self._funding_info_polling_task = safe_ensure_future(self._funding_info_polling_loop())
         if self._trading_required:
+            await self._get_position_mode()
             self._status_polling_task = safe_ensure_future(self._status_polling_loop())
             self._user_stream_tracker_task = safe_ensure_future(self._user_stream_tracker.start())
             self._user_stream_event_listener_task = safe_ensure_future(self._user_stream_event_listener())
@@ -992,32 +994,38 @@ class BinancePerpetualDerivative(ExchangeBase, PerpetualTrading):
             params = {
                 "dualSidePosition": position_mode.value
             }
-            mode = await self.request(
+            response = await self.request(
                 method=MethodType.POST,
                 path=CONSTANTS.CHANGE_POSITION_MODE_URL,
                 params=params,
                 add_timestamp=True,
                 is_signed=True,
-                limit_id=CONSTANTS.POST_POSITION_MODE_LIMIT_ID
+                limit_id=CONSTANTS.POST_POSITION_MODE_LIMIT_ID,
+                return_err=True
             )
-            if mode["msg"] == "success" and mode["code"] == 200:
+            if response["msg"] == "success" and response["code"] == 200:
                 self.logger().info(f"Using {position_mode.name} position mode.")
+                self._position_mode = position_mode
             else:
                 self.logger().error(f"Unable to set postion mode to {position_mode.name}.")
+                self.logger().info(f"Using {initial_mode.name} position mode.")
+                self._position_mode = initial_mode
         else:
             self.logger().info(f"Using {position_mode.name} position mode.")
+            self._position_mode = position_mode
 
-    async def _get_position_mode(self):
+    async def _get_position_mode(self) -> Optional[PositionMode]:
         # To-do: ensure there's no active order or contract before changing position mode
         if self._position_mode is None:
-            mode = await self.request(
+            response = await self.request(
                 method=MethodType.GET,
                 path=CONSTANTS.CHANGE_POSITION_MODE_URL,
                 add_timestamp=True,
                 is_signed=True,
-                limit_id=CONSTANTS.GET_POSITION_MODE_LIMIT_ID
+                limit_id=CONSTANTS.GET_POSITION_MODE_LIMIT_ID,
+                return_err=True
             )
-            self._position_mode = PositionMode.HEDGE if mode["dualSidePosition"] else PositionMode.ONEWAY
+            self._position_mode = PositionMode.HEDGE if response["dualSidePosition"] else PositionMode.ONEWAY
 
         return self._position_mode
 
@@ -1059,7 +1067,7 @@ class BinancePerpetualDerivative(ExchangeBase, PerpetualTrading):
                         if return_err:
                             return error_response
                         else:
-                            raise IOError(f"Error fetching data from {path}. HTTP status is {response.status}. "
+                            raise IOError(f"Error executing request {method.name} {path}. HTTP status is {response.status}. "
                                           f"Request Error: {error_response}")
                     return await response.json()
             except Exception as e:
diff --git a/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_derivative.py b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_derivative.py
index 492d5cc07..ab975f81c 100644
--- a/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_derivative.py
+++ b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_derivative.py
@@ -1,11 +1,17 @@
 import asyncio
 import json
+import pandas as pd
+import re
 import unittest
+
+import hummingbot.connector.derivative.binance_perpetual.constants as CONSTANTS
+import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_utils as utils
+
+from aioresponses.core import aioresponses
 from typing import Any, List, Dict
 from unittest.mock import patch, AsyncMock
 
-import pandas as pd
-
+from hummingbot.core.event.events import PositionMode
 from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative import BinancePerpetualDerivative
 from test.hummingbot.connector.network_mocking_assistant import NetworkMockingAssistant
 
@@ -20,6 +26,7 @@ class BinancePerpetualDerivativeUnitTest(unittest.TestCase):
         cls.quote_asset = "HBOT"
         cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}"
         cls.symbol = f"{cls.base_asset}{cls.quote_asset}"
+        cls.domain = "binance_perpetual_testnet"
 
     @patch("hummingbot.connector.exchange.binance.binance_time.BinanceTime.start")
     def setUp(self, mocked_binance_time_start) -> None:
@@ -37,21 +44,11 @@ class BinancePerpetualDerivativeUnitTest(unittest.TestCase):
             binance_perpetual_api_key="testAPIKey",
             binance_perpetual_api_secret="testSecret",
             trading_pairs=[self.trading_pair],
+            domain=self.domain
         )
 
         self.mocking_assistant = NetworkMockingAssistant()
 
-    async def _get_next_api_response(self):
-        message = await self.api_responses.get()
-        self.api_responses.task_done()
-        return message
-
-    def _set_mock_response(self, mock_api, status: int, json_data: Any, text_data: str = ""):
-        self.api_responses.put_nowait(json_data)
-        mock_api.return_value.status = status
-        mock_api.return_value.json.side_effect = self._get_next_api_response
-        mock_api.return_value.text = AsyncMock(return_value=text_data)
-
     async def _await_all_api_responses_delivered(self):
         await self.api_responses.join()
 
@@ -120,11 +117,13 @@ class BinancePerpetualDerivativeUnitTest(unittest.TestCase):
         }
         return account_update
 
-    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    @aioresponses()
     def test_existing_account_position_detected_on_positions_update(self, req_mock):
+        url = utils.rest_url(CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain, api_version=CONSTANTS.API_VERSION_V2)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
+
         positions = self._get_position_risk_api_endpoint_single_position_list()
-        self.mocking_assistant.configure_http_request_mock(req_mock)
-        self.mocking_assistant.add_http_response(req_mock, 200, positions)
+        req_mock.get(regex_url, body=json.dumps(positions))
 
         task = self.ev_loop.create_task(self.exchange._update_positions())
         self.ev_loop.run_until_complete(task)
@@ -133,11 +132,14 @@ class BinancePerpetualDerivativeUnitTest(unittest.TestCase):
         pos = list(self.exchange.account_positions.values())[0]
         self.assertEqual(pos.trading_pair.replace("-", ""), self.symbol)
 
-    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    @aioresponses()
     def test_account_position_updated_on_positions_update(self, req_mock):
+        url = utils.rest_url(CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain, api_version=CONSTANTS.API_VERSION_V2)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
+
         positions = self._get_position_risk_api_endpoint_single_position_list()
-        self.mocking_assistant.configure_http_request_mock(req_mock)
-        self.mocking_assistant.add_http_response(req_mock, 200, positions)
+        req_mock.get(regex_url, body=json.dumps(positions))
+
         task = self.ev_loop.create_task(self.exchange._update_positions())
         self.ev_loop.run_until_complete(task)
 
@@ -146,52 +148,57 @@ class BinancePerpetualDerivativeUnitTest(unittest.TestCase):
         self.assertEqual(pos.amount, 1)
 
         positions[0]["positionAmt"] = "2"
-        self.mocking_assistant.add_http_response(req_mock, 200, positions)
+        req_mock.get(regex_url, body=json.dumps(positions))
         task = self.ev_loop.create_task(self.exchange._update_positions())
         self.ev_loop.run_until_complete(task)
 
         pos = list(self.exchange.account_positions.values())[0]
         self.assertEqual(pos.amount, 2)
 
-    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    @aioresponses()
     def test_new_account_position_detected_on_positions_update(self, req_mock):
-        self.mocking_assistant.configure_http_request_mock(req_mock)
-        self.mocking_assistant.add_http_response(req_mock, 200, [])
+        url = utils.rest_url(CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain, api_version=CONSTANTS.API_VERSION_V2)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
+
+        req_mock.get(regex_url, body=json.dumps([]))
         task = self.ev_loop.create_task(self.exchange._update_positions())
         self.ev_loop.run_until_complete(task)
 
         self.assertEqual(len(self.exchange.account_positions), 0)
 
         positions = self._get_position_risk_api_endpoint_single_position_list()
-        self.mocking_assistant.add_http_response(req_mock, 200, positions)
+        req_mock.get(regex_url, body=json.dumps(positions))
         task = self.ev_loop.create_task(self.exchange._update_positions())
         self.ev_loop.run_until_complete(task)
 
         self.assertEqual(len(self.exchange.account_positions), 1)
 
-    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    @aioresponses()
     def test_closed_account_position_removed_on_positions_update(self, req_mock):
+        url = utils.rest_url(CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain, api_version=CONSTANTS.API_VERSION_V2)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
+
         positions = self._get_position_risk_api_endpoint_single_position_list()
-        self.mocking_assistant.configure_http_request_mock(req_mock)
-        self.mocking_assistant.add_http_response(req_mock, 200, positions)
+        req_mock.get(regex_url, body=json.dumps(positions))
         task = self.ev_loop.create_task(self.exchange._update_positions())
         self.ev_loop.run_until_complete(task)
 
         self.assertEqual(len(self.exchange.account_positions), 1)
 
         positions[0]["positionAmt"] = "0"
-        self.mocking_assistant.add_http_response(req_mock, 200, positions)
+        req_mock.get(regex_url, body=json.dumps(positions))
         task = self.ev_loop.create_task(self.exchange._update_positions())
         self.ev_loop.run_until_complete(task)
 
         self.assertEqual(len(self.exchange.account_positions), 0)
 
-    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    @aioresponses()
     @patch("websockets.connect", new_callable=AsyncMock)
-    @patch("aiohttp.ClientSession.post", new_callable=AsyncMock)
-    def test_new_account_position_detected_on_stream_event(self, post_mock, ws_connect_mock, req_mock):
-        self.mocking_assistant.configure_http_request_mock(post_mock)
-        self.mocking_assistant.add_http_response(post_mock, 200, {"listenKey": "someListenKey"})
+    def test_new_account_position_detected_on_stream_event(self, mock_api, ws_connect_mock):
+        url = utils.rest_url(CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
+        listen_key_response = {"listenKey": "someListenKey"}
+        mock_api.post(regex_url, body=json.dumps(listen_key_response))
 
         ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock()
         self.ev_loop.create_task(self.exchange._user_stream_tracker.start())
@@ -201,27 +208,32 @@ class BinancePerpetualDerivativeUnitTest(unittest.TestCase):
         account_update = self._get_account_update_ws_event_single_position_dict()
         self.mocking_assistant.add_websocket_text_message(ws_connect_mock.return_value, json.dumps(account_update))
 
+        url = utils.rest_url(CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain, api_version=CONSTANTS.API_VERSION_V2)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
         positions = self._get_position_risk_api_endpoint_single_position_list()
-        self.mocking_assistant.configure_http_request_mock(req_mock)
-        self.mocking_assistant.add_http_response(req_mock, 200, positions)
+        mock_api.get(regex_url, body=json.dumps(positions))
 
         self.ev_loop.create_task(self.exchange._user_stream_event_listener())
-        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.3))
+        asyncio.get_event_loop().run_until_complete(asyncio.sleep(1))
 
         self.assertEqual(len(self.exchange.account_positions), 1)
 
-    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    @aioresponses()
     @patch("websockets.connect", new_callable=AsyncMock)
-    @patch("aiohttp.ClientSession.post", new_callable=AsyncMock)
-    def test_account_position_updated_on_stream_event(self, post_mock, ws_connect_mock, req_mock):
+    def test_account_position_updated_on_stream_event(self, mock_api, ws_connect_mock):
+        url = utils.rest_url(CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain, api_version=CONSTANTS.API_VERSION_V2)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
         positions = self._get_position_risk_api_endpoint_single_position_list()
-        self.mocking_assistant.configure_http_request_mock(req_mock)
-        self.mocking_assistant.add_http_response(req_mock, 200, positions)
+        mock_api.get(regex_url, body=json.dumps(positions))
+
         task = self.ev_loop.create_task(self.exchange._update_positions())
         self.ev_loop.run_until_complete(task)
 
-        self.mocking_assistant.configure_http_request_mock(post_mock)
-        self.mocking_assistant.add_http_response(post_mock, 200, {"listenKey": "someListenKey"})
+        url = utils.rest_url(CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
+        listen_key_response = {"listenKey": "someListenKey"}
+        mock_api.post(regex_url, body=json.dumps(listen_key_response))
+
         ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock()
         self.ev_loop.create_task(self.exchange._user_stream_tracker.start())
         self.ev_loop.run_until_complete(self._await_all_api_responses_delivered())
@@ -241,19 +253,23 @@ class BinancePerpetualDerivativeUnitTest(unittest.TestCase):
         pos = list(self.exchange.account_positions.values())[0]
         self.assertEqual(pos.amount, 2)
 
-    @patch("aiohttp.ClientSession.request", new_callable=AsyncMock)
+    @aioresponses()
     @patch("websockets.connect", new_callable=AsyncMock)
-    @patch("aiohttp.ClientSession.post", new_callable=AsyncMock)
-    def test_closed_account_position_removed_on_stream_event(self, post_mock, ws_connect_mock, req_mock):
+    def test_closed_account_position_removed_on_stream_event(self, mock_api, ws_connect_mock):
+        url = utils.rest_url(CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain, api_version=CONSTANTS.API_VERSION_V2)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
         positions = self._get_position_risk_api_endpoint_single_position_list()
-        self.mocking_assistant.configure_http_request_mock(req_mock)
-        self.mocking_assistant.add_http_response(req_mock, 200, positions)
+        mock_api.get(regex_url, body=json.dumps(positions))
+
         task = self.ev_loop.create_task(self.exchange._update_positions())
         self.ev_loop.run_until_complete(task)
 
-        self.mocking_assistant.configure_http_request_mock(post_mock)
-        self.mocking_assistant.add_http_response(post_mock, 200, {"listenKey": "someListenKey"})
+        url = utils.rest_url(CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
+        listen_key_response = {"listenKey": "someListenKey"}
+        mock_api.post(regex_url, body=json.dumps(listen_key_response))
         ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock()
+
         self.ev_loop.create_task(self.exchange._user_stream_tracker.start())
         self.ev_loop.run_until_complete(self._await_all_api_responses_delivered())
 
@@ -267,3 +283,78 @@ class BinancePerpetualDerivativeUnitTest(unittest.TestCase):
         self.ev_loop.run_until_complete(asyncio.sleep(0.3))
 
         self.assertEqual(len(self.exchange.account_positions), 0)
+
+    @aioresponses()
+    def test_set_position_mode_initial_mode_is_none(self, mock_api):
+        self.assertIsNone(self.exchange.position_mode)
+
+        url = utils.rest_url(CONSTANTS.CHANGE_POSITION_MODE_URL, domain=self.domain)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
+        get_position_mode_response = {
+            "dualSidePosition": False  # True: Hedge Mode; False: One-way Mode
+        }
+        post_position_mode_response = {
+            "code": 200,
+            "msg": "success"
+        }
+        mock_api.get(regex_url, body=json.dumps(get_position_mode_response))
+        mock_api.post(regex_url, body=json.dumps(post_position_mode_response))
+
+        task = self.ev_loop.create_task(self.exchange._set_position_mode(PositionMode.HEDGE))
+        self.ev_loop.run_until_complete(task)
+
+        self.assertEqual(PositionMode.HEDGE, self.exchange.position_mode)
+
+    @aioresponses()
+    def test_set_position_initial_mode_unchanged(self, mock_api):
+        self.exchange._position_mode = PositionMode.ONEWAY
+        url = utils.rest_url(CONSTANTS.CHANGE_POSITION_MODE_URL, domain=self.domain)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
+        get_position_mode_response = {
+            "dualSidePosition": False  # True: Hedge Mode; False: One-way Mode
+        }
+
+        mock_api.get(regex_url, body=json.dumps(get_position_mode_response))
+        task = self.ev_loop.create_task(self.exchange._set_position_mode(PositionMode.ONEWAY))
+        self.ev_loop.run_until_complete(task)
+
+        self.assertEqual(PositionMode.ONEWAY, self.exchange.position_mode)
+
+    @aioresponses()
+    def test_set_position_mode_diff_initial_mode_change_successful(self, mock_api):
+        self.exchange._position_mode = PositionMode.ONEWAY
+        url = utils.rest_url(CONSTANTS.CHANGE_POSITION_MODE_URL, domain=self.domain)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
+        get_position_mode_response = {
+            "dualSidePosition": False  # True: Hedge Mode; False: One-way Mode
+        }
+        post_position_mode_response = {
+            "code": 200,
+            "msg": "success"
+        }
+
+        mock_api.get(regex_url, body=json.dumps(get_position_mode_response))
+        mock_api.post(regex_url, body=json.dumps(post_position_mode_response))
+
+        task = self.ev_loop.create_task(self.exchange._set_position_mode(PositionMode.HEDGE))
+        self.ev_loop.run_until_complete(task)
+
+        self.assertEqual(PositionMode.HEDGE, self.exchange.position_mode)
+
+    @aioresponses()
+    def test_set_position_mode_diff_initial_mode_change_fail(self, mock_api):
+        self.exchange._position_mode = PositionMode.ONEWAY
+        url = utils.rest_url(CONSTANTS.CHANGE_POSITION_MODE_URL, domain=self.domain)
+        regex_url = re.compile(f"^{url}".replace(".", r"\\.").replace("?", r"\\?"))
+        get_position_mode_response = {
+            "dualSidePosition": False  # True: Hedge Mode; False: One-way Mode
+        }
+        post_position_mode_response = {'code': -4059, 'msg': 'No need to change position side.'}
+
+        mock_api.get(regex_url, body=json.dumps(get_position_mode_response))
+        mock_api.post(regex_url, body=json.dumps(post_position_mode_response))
+
+        task = self.ev_loop.create_task(self.exchange._set_position_mode(PositionMode.HEDGE))
+        self.ev_loop.run_until_complete(task)
+
+        self.assertEqual(PositionMode.ONEWAY, self.exchange.position_mode)