We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bitmex API request failed due to: Signature not valid.
I added a Bitmex API key with no special permissions:

<img width="925" alt="Screen Shot 2020-08-07 at 12 30 54" src="https://user-images.githubusercontent.com/2758453/89637013-d900c380-d8a9-11ea-82c1-9a114a5aa6b5.png">

In rotki, I get:

```
[07/08/2020 12:25:54 CEST] rotkehlchen.exchanges.bitmex: Bitmex API request failed due to: Signature not valid.
```

Update:

```
python -m pytest rotkehlchen/tests/exchanges/test_bitmex.py::test_bitmex_api_signature
// passed
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `rotkehlchen/exchanges/bitmex.py`
Content:
```
1 import hashlib
2 import hmac
3 import logging
4 import time
5 from json.decoder import JSONDecodeError
6 from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
7 from urllib.parse import urlencode
8 
9 import requests
10 
11 from rotkehlchen.assets.asset import Asset
12 from rotkehlchen.constants.assets import A_BTC
13 from rotkehlchen.errors import DeserializationError, RemoteError, UnknownAsset
14 from rotkehlchen.exchanges.data_structures import AssetMovement, Location, MarginPosition
15 from rotkehlchen.exchanges.exchange import ExchangeInterface
16 from rotkehlchen.exchanges.utils import deserialize_asset_movement_address, get_key_if_has_val
17 from rotkehlchen.fval import FVal
18 from rotkehlchen.inquirer import Inquirer
19 from rotkehlchen.logging import RotkehlchenLogsAdapter
20 from rotkehlchen.serialization.deserialize import (
21     deserialize_asset_amount_force_positive,
22     deserialize_fee,
23 )
24 from rotkehlchen.typing import (
25     ApiKey,
26     ApiSecret,
27     AssetAmount,
28     AssetMovementCategory,
29     Fee,
30     Timestamp,
31 )
32 from rotkehlchen.user_messages import MessagesAggregator
33 from rotkehlchen.utils.interfaces import cache_response_timewise, protect_with_lock
34 from rotkehlchen.utils.misc import iso8601ts_to_timestamp, satoshis_to_btc
35 from rotkehlchen.utils.serialization import rlk_jsonloads
36 
37 if TYPE_CHECKING:
38     from rotkehlchen.db.dbhandler import DBHandler
39 
40 logger = logging.getLogger(__name__)
41 log = RotkehlchenLogsAdapter(logger)
42 
43 BITMEX_PRIVATE_ENDPOINTS = (
44     'user',
45     'user/wallet',
46     'user/walletHistory',
47 )
48 
49 
50 def bitmex_to_world(symbol: str) -> Asset:
51     if symbol == 'XBt':
52         return A_BTC
53     return Asset(symbol)
54 
55 
56 def trade_from_bitmex(bitmex_trade: Dict) -> MarginPosition:
57     """Turn a bitmex trade returned from bitmex trade history to our common trade
58     history format. This only returns margin positions as bitmex only deals in
59     margin trading"""
60     close_time = iso8601ts_to_timestamp(bitmex_trade['transactTime'])
61     profit_loss = AssetAmount(satoshis_to_btc(FVal(bitmex_trade['amount'])))
62     currency = bitmex_to_world(bitmex_trade['currency'])
63     fee = deserialize_fee(bitmex_trade['fee'])
64     notes = bitmex_trade['address']
65     assert currency == A_BTC, 'Bitmex trade should only deal in BTC'
66 
67     log.debug(
68         'Processing Bitmex Trade',
69         sensitive_log=True,
70         timestamp=close_time,
71         profit_loss=profit_loss,
72         currency=currency,
73         fee=fee,
74         notes=notes,
75     )
76 
77     return MarginPosition(
78         location=Location.BITMEX,
79         open_time=None,
80         close_time=close_time,
81         profit_loss=profit_loss,
82         pl_currency=currency,
83         fee=fee,
84         fee_currency=A_BTC,
85         notes=notes,
86         link=str(bitmex_trade['transactID']),
87     )
88 
89 
90 class Bitmex(ExchangeInterface):
91     def __init__(
92             self,
93             api_key: ApiKey,
94             secret: ApiSecret,
95             database: 'DBHandler',
96             msg_aggregator: MessagesAggregator,
97     ):
98         super(Bitmex, self).__init__('bitmex', api_key, secret, database)
99         self.uri = 'https://bitmex.com'
100         self.session.headers.update({'api-key': api_key})
101         self.msg_aggregator = msg_aggregator
102 
103     def first_connection(self) -> None:
104         self.first_connection_made = True
105 
106     def validate_api_key(self) -> Tuple[bool, str]:
107         try:
108             self._api_query('get', 'user')
109         except RemoteError as e:
110             error = str(e)
111             if 'Invalid API Key' in error:
112                 return False, 'Provided API Key is invalid'
113             if 'Signature not valid' in error:
114                 return False, 'Provided API Secret is invalid'
115             # else reraise
116             raise
117         return True, ''
118 
119     def _generate_signature(self, verb: str, path: str, expires: int, data: str = '') -> str:
120         signature = hmac.new(
121             self.secret,
122             (verb.upper() + path + str(expires) + data).encode(),
123             hashlib.sha256,
124         ).hexdigest()
125         self.session.headers.update({
126             'api-signature': signature,
127         })
128         return signature
129 
130     def _api_query(
131             self,
132             verb: str,
133             path: str,
134             options: Optional[Dict] = None,
135     ) -> Union[List, Dict]:
136         """
137         Queries Bitmex with the given verb for the given path and options
138         """
139         assert verb in ('get', 'post', 'push'), (
140             'Given verb {} is not a valid HTTP verb'.format(verb)
141         )
142 
143         # 20 seconds expiration
144         expires = int(time.time()) + 20
145 
146         request_path_no_args = '/api/v1/' + path
147 
148         data = ''
149         if not options:
150             request_path = request_path_no_args
151         else:
152             request_path = request_path_no_args + '?' + urlencode(options)
153 
154         if path in BITMEX_PRIVATE_ENDPOINTS:
155             self._generate_signature(
156                 verb=verb,
157                 path=request_path,
158                 expires=expires,
159                 data=data,
160             )
161 
162         self.session.headers.update({
163             'api-expires': str(expires),
164         })
165         if data != '':
166             self.session.headers.update({
167                 'Content-Type': 'application/json',
168                 'Content-Length': str(len(data)),
169             })
170 
171         request_url = self.uri + request_path
172         log.debug('Bitmex API Query', verb=verb, request_url=request_url)
173         try:
174             response = getattr(self.session, verb)(request_url, data=data)
175         except requests.exceptions.RequestException as e:
176             raise RemoteError(f'Bitmex API request failed due to {str(e)}') from e
177 
178         if response.status_code not in (200, 401):
179             raise RemoteError(
180                 'Bitmex api request for {} failed with HTTP status code {}'.format(
181                     response.url,
182                     response.status_code,
183                 ),
184             )
185 
186         try:
187             json_ret = rlk_jsonloads(response.text)
188         except JSONDecodeError as e:
189             raise RemoteError('Bitmex returned invalid JSON response') from e
190 
191         if isinstance(json_ret, dict) and 'error' in json_ret:
192             raise RemoteError(json_ret['error']['message'])
193 
194         return json_ret
195 
196     def _api_query_dict(
197             self,
198             verb: str,
199             path: str,
200             options: Optional[Dict] = None,
201     ) -> Dict:
202         result = self._api_query(verb, path, options)
203         assert isinstance(result, Dict)
204         return result
205 
206     def _api_query_list(
207             self,
208             verb: str,
209             path: str,
210             options: Optional[Dict] = None,
211     ) -> List:
212         result = self._api_query(verb, path, options)
213         assert isinstance(result, List)
214         return result
215 
216     @protect_with_lock()
217     @cache_response_timewise()
218     def query_balances(self) -> Tuple[Optional[dict], str]:
219 
220         try:
221             resp = self._api_query_dict('get', 'user/wallet', {'currency': 'XBt'})
222             # Bitmex shows only BTC balance
223             returned_balances = {}
224             usd_price = Inquirer().find_usd_price(A_BTC)
225         except RemoteError as e:
226             msg = f'Bitmex API request failed due to: {str(e)}'
227             log.error(msg)
228             return None, msg
229 
230         # result is in satoshis
231         amount = satoshis_to_btc(FVal(resp['amount']))
232         usd_value = amount * usd_price
233 
234         returned_balances[A_BTC] = {
235             'amount': amount,
236             'usd_value': usd_value,
237         }
238         log.debug(
239             'Bitmex balance query result',
240             sensitive_log=True,
241             currency='BTC',
242             amount=amount,
243             usd_value=usd_value,
244         )
245 
246         return returned_balances, ''
247 
248     def query_online_margin_history(
249             self,
250             start_ts: Timestamp,
251             end_ts: Timestamp,
252     ) -> List[MarginPosition]:
253 
254         # We know user/walletHistory returns a list
255         resp = self._api_query_list('get', 'user/walletHistory')
256         log.debug('Bitmex trade history query', results_num=len(resp))
257 
258         margin_trades = []
259         for tx in resp:
260             if tx['timestamp'] is None:
261                 timestamp = None
262             else:
263                 timestamp = iso8601ts_to_timestamp(tx['timestamp'])
264             if tx['transactType'] != 'RealisedPNL':
265                 continue
266             if timestamp and timestamp < start_ts:
267                 continue
268             if timestamp and timestamp > end_ts:
269                 continue
270             margin_trades.append(trade_from_bitmex(tx))
271 
272         return margin_trades
273 
274     def query_online_deposits_withdrawals(
275             self,
276             start_ts: Timestamp,
277             end_ts: Timestamp,
278     ) -> List:
279         resp = self._api_query_list('get', 'user/walletHistory')
280 
281         log.debug('Bitmex deposit/withdrawals query', results_num=len(resp))
282 
283         movements = []
284         for movement in resp:
285             try:
286                 transaction_type = movement['transactType']
287                 if transaction_type == 'Deposit':
288                     transaction_type = AssetMovementCategory.DEPOSIT
289                 elif transaction_type == 'Withdrawal':
290                     transaction_type = AssetMovementCategory.WITHDRAWAL
291                 else:
292                     continue
293 
294                 timestamp = iso8601ts_to_timestamp(movement['timestamp'])
295                 if timestamp < start_ts:
296                     continue
297                 if timestamp > end_ts:
298                     continue
299 
300                 asset = bitmex_to_world(movement['currency'])
301                 amount = deserialize_asset_amount_force_positive(movement['amount'])
302                 fee = deserialize_fee(movement['fee'])
303 
304                 if asset == A_BTC:
305                     # bitmex stores amounts in satoshis
306                     amount = AssetAmount(satoshis_to_btc(amount))
307                     fee = Fee(satoshis_to_btc(fee))
308 
309                 movements.append(AssetMovement(
310                     location=Location.BITMEX,
311                     category=transaction_type,
312                     address=deserialize_asset_movement_address(movement, 'address', asset),
313                     transaction_id=get_key_if_has_val(movement, 'tx'),
314                     timestamp=timestamp,
315                     asset=asset,
316                     amount=amount,
317                     fee_asset=asset,
318                     fee=fee,
319                     link=str(movement['transactID']),
320                 ))
321             except UnknownAsset as e:
322                 self.msg_aggregator.add_warning(
323                     f'Found bitmex deposit/withdrawal with unknown asset '
324                     f'{e.asset_name}. Ignoring it.',
325                 )
326                 continue
327             except (DeserializationError, KeyError) as e:
328                 msg = str(e)
329                 if isinstance(e, KeyError):
330                     msg = f'Missing key entry for {msg}.'
331                 self.msg_aggregator.add_error(
332                     'Unexpected data encountered during deserialization of a bitmex '
333                     'asset movement. Check logs for details and open a bug report.',
334                 )
335                 log.error(
336                     f'Unexpected data encountered during deserialization of bitmex '
337                     f'asset_movement {movement}. Error was: {msg}',
338                 )
339                 continue
340         return movements
341
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/rotkehlchen/exchanges/bitmex.py b/rotkehlchen/exchanges/bitmex.py
--- a/rotkehlchen/exchanges/bitmex.py
+++ b/rotkehlchen/exchanges/bitmex.py
@@ -148,13 +148,15 @@
         data = ''
         if not options:
             request_path = request_path_no_args
+            signature_path = request_path
         else:
             request_path = request_path_no_args + '?' + urlencode(options)
+            signature_path = request_path_no_args if path == 'user/wallet' else request_path
 
         if path in BITMEX_PRIVATE_ENDPOINTS:
             self._generate_signature(
                 verb=verb,
-                path=request_path,
+                path=signature_path,
                 expires=expires,
                 data=data,
             )

{"golden_diff": "diff --git a/rotkehlchen/exchanges/bitmex.py b/rotkehlchen/exchanges/bitmex.py\n--- a/rotkehlchen/exchanges/bitmex.py\n+++ b/rotkehlchen/exchanges/bitmex.py\n@@ -148,13 +148,15 @@\n         data = ''\n         if not options:\n             request_path = request_path_no_args\n+            signature_path = request_path\n         else:\n             request_path = request_path_no_args + '?' + urlencode(options)\n+            signature_path = request_path_no_args if path == 'user/wallet' else request_path\n \n         if path in BITMEX_PRIVATE_ENDPOINTS:\n             self._generate_signature(\n                 verb=verb,\n-                path=request_path,\n+                path=signature_path,\n                 expires=expires,\n                 data=data,\n             )\n", "issue": "Bitmex API request failed due to: Signature not valid.\nI added a Bitmex API key with no special permissions:\r\n\r\n<img width=\"925\" alt=\"Screen Shot 2020-08-07 at 12 30 54\" src=\"https://user-images.githubusercontent.com/2758453/89637013-d900c380-d8a9-11ea-82c1-9a114a5aa6b5.png\">\r\n\r\nIn rotki, I get:\r\n\r\n```\r\n[07/08/2020 12:25:54 CEST] rotkehlchen.exchanges.bitmex: Bitmex API request failed due to: Signature not valid.\r\n```\r\n\r\nUpdate:\r\n\r\n```\r\npython -m pytest rotkehlchen/tests/exchanges/test_bitmex.py::test_bitmex_api_signature\r\n// passed\r\n```\n", "before_files": [{"content": "import hashlib\nimport hmac\nimport logging\nimport time\nfrom json.decoder import JSONDecodeError\nfrom typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union\nfrom urllib.parse import urlencode\n\nimport requests\n\nfrom rotkehlchen.assets.asset import Asset\nfrom rotkehlchen.constants.assets import A_BTC\nfrom rotkehlchen.errors import DeserializationError, RemoteError, UnknownAsset\nfrom rotkehlchen.exchanges.data_structures import AssetMovement, Location, MarginPosition\nfrom rotkehlchen.exchanges.exchange import ExchangeInterface\nfrom rotkehlchen.exchanges.utils import deserialize_asset_movement_address, get_key_if_has_val\nfrom rotkehlchen.fval import FVal\nfrom rotkehlchen.inquirer import Inquirer\nfrom rotkehlchen.logging import RotkehlchenLogsAdapter\nfrom rotkehlchen.serialization.deserialize import (\n    deserialize_asset_amount_force_positive,\n    deserialize_fee,\n)\nfrom rotkehlchen.typing import (\n    ApiKey,\n    ApiSecret,\n    AssetAmount,\n    AssetMovementCategory,\n    Fee,\n    Timestamp,\n)\nfrom rotkehlchen.user_messages import MessagesAggregator\nfrom rotkehlchen.utils.interfaces import cache_response_timewise, protect_with_lock\nfrom rotkehlchen.utils.misc import iso8601ts_to_timestamp, satoshis_to_btc\nfrom rotkehlchen.utils.serialization import rlk_jsonloads\n\nif TYPE_CHECKING:\n    from rotkehlchen.db.dbhandler import DBHandler\n\nlogger = logging.getLogger(__name__)\nlog = RotkehlchenLogsAdapter(logger)\n\nBITMEX_PRIVATE_ENDPOINTS = (\n    'user',\n    'user/wallet',\n    'user/walletHistory',\n)\n\n\ndef bitmex_to_world(symbol: str) -> Asset:\n    if symbol == 'XBt':\n        return A_BTC\n    return Asset(symbol)\n\n\ndef trade_from_bitmex(bitmex_trade: Dict) -> MarginPosition:\n    \"\"\"Turn a bitmex trade returned from bitmex trade history to our common trade\n    history format. This only returns margin positions as bitmex only deals in\n    margin trading\"\"\"\n    close_time = iso8601ts_to_timestamp(bitmex_trade['transactTime'])\n    profit_loss = AssetAmount(satoshis_to_btc(FVal(bitmex_trade['amount'])))\n    currency = bitmex_to_world(bitmex_trade['currency'])\n    fee = deserialize_fee(bitmex_trade['fee'])\n    notes = bitmex_trade['address']\n    assert currency == A_BTC, 'Bitmex trade should only deal in BTC'\n\n    log.debug(\n        'Processing Bitmex Trade',\n        sensitive_log=True,\n        timestamp=close_time,\n        profit_loss=profit_loss,\n        currency=currency,\n        fee=fee,\n        notes=notes,\n    )\n\n    return MarginPosition(\n        location=Location.BITMEX,\n        open_time=None,\n        close_time=close_time,\n        profit_loss=profit_loss,\n        pl_currency=currency,\n        fee=fee,\n        fee_currency=A_BTC,\n        notes=notes,\n        link=str(bitmex_trade['transactID']),\n    )\n\n\nclass Bitmex(ExchangeInterface):\n    def __init__(\n            self,\n            api_key: ApiKey,\n            secret: ApiSecret,\n            database: 'DBHandler',\n            msg_aggregator: MessagesAggregator,\n    ):\n        super(Bitmex, self).__init__('bitmex', api_key, secret, database)\n        self.uri = 'https://bitmex.com'\n        self.session.headers.update({'api-key': api_key})\n        self.msg_aggregator = msg_aggregator\n\n    def first_connection(self) -> None:\n        self.first_connection_made = True\n\n    def validate_api_key(self) -> Tuple[bool, str]:\n        try:\n            self._api_query('get', 'user')\n        except RemoteError as e:\n            error = str(e)\n            if 'Invalid API Key' in error:\n                return False, 'Provided API Key is invalid'\n            if 'Signature not valid' in error:\n                return False, 'Provided API Secret is invalid'\n            # else reraise\n            raise\n        return True, ''\n\n    def _generate_signature(self, verb: str, path: str, expires: int, data: str = '') -> str:\n        signature = hmac.new(\n            self.secret,\n            (verb.upper() + path + str(expires) + data).encode(),\n            hashlib.sha256,\n        ).hexdigest()\n        self.session.headers.update({\n            'api-signature': signature,\n        })\n        return signature\n\n    def _api_query(\n            self,\n            verb: str,\n            path: str,\n            options: Optional[Dict] = None,\n    ) -> Union[List, Dict]:\n        \"\"\"\n        Queries Bitmex with the given verb for the given path and options\n        \"\"\"\n        assert verb in ('get', 'post', 'push'), (\n            'Given verb {} is not a valid HTTP verb'.format(verb)\n        )\n\n        # 20 seconds expiration\n        expires = int(time.time()) + 20\n\n        request_path_no_args = '/api/v1/' + path\n\n        data = ''\n        if not options:\n            request_path = request_path_no_args\n        else:\n            request_path = request_path_no_args + '?' + urlencode(options)\n\n        if path in BITMEX_PRIVATE_ENDPOINTS:\n            self._generate_signature(\n                verb=verb,\n                path=request_path,\n                expires=expires,\n                data=data,\n            )\n\n        self.session.headers.update({\n            'api-expires': str(expires),\n        })\n        if data != '':\n            self.session.headers.update({\n                'Content-Type': 'application/json',\n                'Content-Length': str(len(data)),\n            })\n\n        request_url = self.uri + request_path\n        log.debug('Bitmex API Query', verb=verb, request_url=request_url)\n        try:\n            response = getattr(self.session, verb)(request_url, data=data)\n        except requests.exceptions.RequestException as e:\n            raise RemoteError(f'Bitmex API request failed due to {str(e)}') from e\n\n        if response.status_code not in (200, 401):\n            raise RemoteError(\n                'Bitmex api request for {} failed with HTTP status code {}'.format(\n                    response.url,\n                    response.status_code,\n                ),\n            )\n\n        try:\n            json_ret = rlk_jsonloads(response.text)\n        except JSONDecodeError as e:\n            raise RemoteError('Bitmex returned invalid JSON response') from e\n\n        if isinstance(json_ret, dict) and 'error' in json_ret:\n            raise RemoteError(json_ret['error']['message'])\n\n        return json_ret\n\n    def _api_query_dict(\n            self,\n            verb: str,\n            path: str,\n            options: Optional[Dict] = None,\n    ) -> Dict:\n        result = self._api_query(verb, path, options)\n        assert isinstance(result, Dict)\n        return result\n\n    def _api_query_list(\n            self,\n            verb: str,\n            path: str,\n            options: Optional[Dict] = None,\n    ) -> List:\n        result = self._api_query(verb, path, options)\n        assert isinstance(result, List)\n        return result\n\n    @protect_with_lock()\n    @cache_response_timewise()\n    def query_balances(self) -> Tuple[Optional[dict], str]:\n\n        try:\n            resp = self._api_query_dict('get', 'user/wallet', {'currency': 'XBt'})\n            # Bitmex shows only BTC balance\n            returned_balances = {}\n            usd_price = Inquirer().find_usd_price(A_BTC)\n        except RemoteError as e:\n            msg = f'Bitmex API request failed due to: {str(e)}'\n            log.error(msg)\n            return None, msg\n\n        # result is in satoshis\n        amount = satoshis_to_btc(FVal(resp['amount']))\n        usd_value = amount * usd_price\n\n        returned_balances[A_BTC] = {\n            'amount': amount,\n            'usd_value': usd_value,\n        }\n        log.debug(\n            'Bitmex balance query result',\n            sensitive_log=True,\n            currency='BTC',\n            amount=amount,\n            usd_value=usd_value,\n        )\n\n        return returned_balances, ''\n\n    def query_online_margin_history(\n            self,\n            start_ts: Timestamp,\n            end_ts: Timestamp,\n    ) -> List[MarginPosition]:\n\n        # We know user/walletHistory returns a list\n        resp = self._api_query_list('get', 'user/walletHistory')\n        log.debug('Bitmex trade history query', results_num=len(resp))\n\n        margin_trades = []\n        for tx in resp:\n            if tx['timestamp'] is None:\n                timestamp = None\n            else:\n                timestamp = iso8601ts_to_timestamp(tx['timestamp'])\n            if tx['transactType'] != 'RealisedPNL':\n                continue\n            if timestamp and timestamp < start_ts:\n                continue\n            if timestamp and timestamp > end_ts:\n                continue\n            margin_trades.append(trade_from_bitmex(tx))\n\n        return margin_trades\n\n    def query_online_deposits_withdrawals(\n            self,\n            start_ts: Timestamp,\n            end_ts: Timestamp,\n    ) -> List:\n        resp = self._api_query_list('get', 'user/walletHistory')\n\n        log.debug('Bitmex deposit/withdrawals query', results_num=len(resp))\n\n        movements = []\n        for movement in resp:\n            try:\n                transaction_type = movement['transactType']\n                if transaction_type == 'Deposit':\n                    transaction_type = AssetMovementCategory.DEPOSIT\n                elif transaction_type == 'Withdrawal':\n                    transaction_type = AssetMovementCategory.WITHDRAWAL\n                else:\n                    continue\n\n                timestamp = iso8601ts_to_timestamp(movement['timestamp'])\n                if timestamp < start_ts:\n                    continue\n                if timestamp > end_ts:\n                    continue\n\n                asset = bitmex_to_world(movement['currency'])\n                amount = deserialize_asset_amount_force_positive(movement['amount'])\n                fee = deserialize_fee(movement['fee'])\n\n                if asset == A_BTC:\n                    # bitmex stores amounts in satoshis\n                    amount = AssetAmount(satoshis_to_btc(amount))\n                    fee = Fee(satoshis_to_btc(fee))\n\n                movements.append(AssetMovement(\n                    location=Location.BITMEX,\n                    category=transaction_type,\n                    address=deserialize_asset_movement_address(movement, 'address', asset),\n                    transaction_id=get_key_if_has_val(movement, 'tx'),\n                    timestamp=timestamp,\n                    asset=asset,\n                    amount=amount,\n                    fee_asset=asset,\n                    fee=fee,\n                    link=str(movement['transactID']),\n                ))\n            except UnknownAsset as e:\n                self.msg_aggregator.add_warning(\n                    f'Found bitmex deposit/withdrawal with unknown asset '\n                    f'{e.asset_name}. Ignoring it.',\n                )\n                continue\n            except (DeserializationError, KeyError) as e:\n                msg = str(e)\n                if isinstance(e, KeyError):\n                    msg = f'Missing key entry for {msg}.'\n                self.msg_aggregator.add_error(\n                    'Unexpected data encountered during deserialization of a bitmex '\n                    'asset movement. Check logs for details and open a bug report.',\n                )\n                log.error(\n                    f'Unexpected data encountered during deserialization of bitmex '\n                    f'asset_movement {movement}. Error was: {msg}',\n                )\n                continue\n        return movements\n", "path": "rotkehlchen/exchanges/bitmex.py"}], "after_files": [{"content": "import hashlib\nimport hmac\nimport logging\nimport time\nfrom json.decoder import JSONDecodeError\nfrom typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union\nfrom urllib.parse import urlencode\n\nimport requests\n\nfrom rotkehlchen.assets.asset import Asset\nfrom rotkehlchen.constants.assets import A_BTC\nfrom rotkehlchen.errors import DeserializationError, RemoteError, UnknownAsset\nfrom rotkehlchen.exchanges.data_structures import AssetMovement, Location, MarginPosition\nfrom rotkehlchen.exchanges.exchange import ExchangeInterface\nfrom rotkehlchen.exchanges.utils import deserialize_asset_movement_address, get_key_if_has_val\nfrom rotkehlchen.fval import FVal\nfrom rotkehlchen.inquirer import Inquirer\nfrom rotkehlchen.logging import RotkehlchenLogsAdapter\nfrom rotkehlchen.serialization.deserialize import (\n    deserialize_asset_amount_force_positive,\n    deserialize_fee,\n)\nfrom rotkehlchen.typing import (\n    ApiKey,\n    ApiSecret,\n    AssetAmount,\n    AssetMovementCategory,\n    Fee,\n    Timestamp,\n)\nfrom rotkehlchen.user_messages import MessagesAggregator\nfrom rotkehlchen.utils.interfaces import cache_response_timewise, protect_with_lock\nfrom rotkehlchen.utils.misc import iso8601ts_to_timestamp, satoshis_to_btc\nfrom rotkehlchen.utils.serialization import rlk_jsonloads\n\nif TYPE_CHECKING:\n    from rotkehlchen.db.dbhandler import DBHandler\n\nlogger = logging.getLogger(__name__)\nlog = RotkehlchenLogsAdapter(logger)\n\nBITMEX_PRIVATE_ENDPOINTS = (\n    'user',\n    'user/wallet',\n    'user/walletHistory',\n)\n\n\ndef bitmex_to_world(symbol: str) -> Asset:\n    if symbol == 'XBt':\n        return A_BTC\n    return Asset(symbol)\n\n\ndef trade_from_bitmex(bitmex_trade: Dict) -> MarginPosition:\n    \"\"\"Turn a bitmex trade returned from bitmex trade history to our common trade\n    history format. This only returns margin positions as bitmex only deals in\n    margin trading\"\"\"\n    close_time = iso8601ts_to_timestamp(bitmex_trade['transactTime'])\n    profit_loss = AssetAmount(satoshis_to_btc(FVal(bitmex_trade['amount'])))\n    currency = bitmex_to_world(bitmex_trade['currency'])\n    fee = deserialize_fee(bitmex_trade['fee'])\n    notes = bitmex_trade['address']\n    assert currency == A_BTC, 'Bitmex trade should only deal in BTC'\n\n    log.debug(\n        'Processing Bitmex Trade',\n        sensitive_log=True,\n        timestamp=close_time,\n        profit_loss=profit_loss,\n        currency=currency,\n        fee=fee,\n        notes=notes,\n    )\n\n    return MarginPosition(\n        location=Location.BITMEX,\n        open_time=None,\n        close_time=close_time,\n        profit_loss=profit_loss,\n        pl_currency=currency,\n        fee=fee,\n        fee_currency=A_BTC,\n        notes=notes,\n        link=str(bitmex_trade['transactID']),\n    )\n\n\nclass Bitmex(ExchangeInterface):\n    def __init__(\n            self,\n            api_key: ApiKey,\n            secret: ApiSecret,\n            database: 'DBHandler',\n            msg_aggregator: MessagesAggregator,\n    ):\n        super(Bitmex, self).__init__('bitmex', api_key, secret, database)\n        self.uri = 'https://bitmex.com'\n        self.session.headers.update({'api-key': api_key})\n        self.msg_aggregator = msg_aggregator\n\n    def first_connection(self) -> None:\n        self.first_connection_made = True\n\n    def validate_api_key(self) -> Tuple[bool, str]:\n        try:\n            self._api_query('get', 'user')\n        except RemoteError as e:\n            error = str(e)\n            if 'Invalid API Key' in error:\n                return False, 'Provided API Key is invalid'\n            if 'Signature not valid' in error:\n                return False, 'Provided API Secret is invalid'\n            # else reraise\n            raise\n        return True, ''\n\n    def _generate_signature(self, verb: str, path: str, expires: int, data: str = '') -> str:\n        signature = hmac.new(\n            self.secret,\n            (verb.upper() + path + str(expires) + data).encode(),\n            hashlib.sha256,\n        ).hexdigest()\n        self.session.headers.update({\n            'api-signature': signature,\n        })\n        return signature\n\n    def _api_query(\n            self,\n            verb: str,\n            path: str,\n            options: Optional[Dict] = None,\n    ) -> Union[List, Dict]:\n        \"\"\"\n        Queries Bitmex with the given verb for the given path and options\n        \"\"\"\n        assert verb in ('get', 'post', 'push'), (\n            'Given verb {} is not a valid HTTP verb'.format(verb)\n        )\n\n        # 20 seconds expiration\n        expires = int(time.time()) + 20\n\n        request_path_no_args = '/api/v1/' + path\n\n        data = ''\n        if not options:\n            request_path = request_path_no_args\n            signature_path = request_path\n        else:\n            request_path = request_path_no_args + '?' + urlencode(options)\n            signature_path = request_path_no_args if path == 'user/wallet' else request_path\n\n        if path in BITMEX_PRIVATE_ENDPOINTS:\n            self._generate_signature(\n                verb=verb,\n                path=signature_path,\n                expires=expires,\n                data=data,\n            )\n\n        self.session.headers.update({\n            'api-expires': str(expires),\n        })\n        if data != '':\n            self.session.headers.update({\n                'Content-Type': 'application/json',\n                'Content-Length': str(len(data)),\n            })\n\n        request_url = self.uri + request_path\n        log.debug('Bitmex API Query', verb=verb, request_url=request_url)\n        try:\n            response = getattr(self.session, verb)(request_url, data=data)\n        except requests.exceptions.RequestException as e:\n            raise RemoteError(f'Bitmex API request failed due to {str(e)}') from e\n\n        if response.status_code not in (200, 401):\n            raise RemoteError(\n                'Bitmex api request for {} failed with HTTP status code {}'.format(\n                    response.url,\n                    response.status_code,\n                ),\n            )\n\n        try:\n            json_ret = rlk_jsonloads(response.text)\n        except JSONDecodeError as e:\n            raise RemoteError('Bitmex returned invalid JSON response') from e\n\n        if isinstance(json_ret, dict) and 'error' in json_ret:\n            raise RemoteError(json_ret['error']['message'])\n\n        return json_ret\n\n    def _api_query_dict(\n            self,\n            verb: str,\n            path: str,\n            options: Optional[Dict] = None,\n    ) -> Dict:\n        result = self._api_query(verb, path, options)\n        assert isinstance(result, Dict)\n        return result\n\n    def _api_query_list(\n            self,\n            verb: str,\n            path: str,\n            options: Optional[Dict] = None,\n    ) -> List:\n        result = self._api_query(verb, path, options)\n        assert isinstance(result, List)\n        return result\n\n    @protect_with_lock()\n    @cache_response_timewise()\n    def query_balances(self) -> Tuple[Optional[dict], str]:\n\n        try:\n            resp = self._api_query_dict('get', 'user/wallet', {'currency': 'XBt'})\n            # Bitmex shows only BTC balance\n            returned_balances = {}\n            usd_price = Inquirer().find_usd_price(A_BTC)\n        except RemoteError as e:\n            msg = f'Bitmex API request failed due to: {str(e)}'\n            log.error(msg)\n            return None, msg\n\n        # result is in satoshis\n        amount = satoshis_to_btc(FVal(resp['amount']))\n        usd_value = amount * usd_price\n\n        returned_balances[A_BTC] = {\n            'amount': amount,\n            'usd_value': usd_value,\n        }\n        log.debug(\n            'Bitmex balance query result',\n            sensitive_log=True,\n            currency='BTC',\n            amount=amount,\n            usd_value=usd_value,\n        )\n\n        return returned_balances, ''\n\n    def query_online_margin_history(\n            self,\n            start_ts: Timestamp,\n            end_ts: Timestamp,\n    ) -> List[MarginPosition]:\n\n        # We know user/walletHistory returns a list\n        resp = self._api_query_list('get', 'user/walletHistory')\n        log.debug('Bitmex trade history query', results_num=len(resp))\n\n        margin_trades = []\n        for tx in resp:\n            if tx['timestamp'] is None:\n                timestamp = None\n            else:\n                timestamp = iso8601ts_to_timestamp(tx['timestamp'])\n            if tx['transactType'] != 'RealisedPNL':\n                continue\n            if timestamp and timestamp < start_ts:\n                continue\n            if timestamp and timestamp > end_ts:\n                continue\n            margin_trades.append(trade_from_bitmex(tx))\n\n        return margin_trades\n\n    def query_online_deposits_withdrawals(\n            self,\n            start_ts: Timestamp,\n            end_ts: Timestamp,\n    ) -> List:\n        resp = self._api_query_list('get', 'user/walletHistory')\n\n        log.debug('Bitmex deposit/withdrawals query', results_num=len(resp))\n\n        movements = []\n        for movement in resp:\n            try:\n                transaction_type = movement['transactType']\n                if transaction_type == 'Deposit':\n                    transaction_type = AssetMovementCategory.DEPOSIT\n                elif transaction_type == 'Withdrawal':\n                    transaction_type = AssetMovementCategory.WITHDRAWAL\n                else:\n                    continue\n\n                timestamp = iso8601ts_to_timestamp(movement['timestamp'])\n                if timestamp < start_ts:\n                    continue\n                if timestamp > end_ts:\n                    continue\n\n                asset = bitmex_to_world(movement['currency'])\n                amount = deserialize_asset_amount_force_positive(movement['amount'])\n                fee = deserialize_fee(movement['fee'])\n\n                if asset == A_BTC:\n                    # bitmex stores amounts in satoshis\n                    amount = AssetAmount(satoshis_to_btc(amount))\n                    fee = Fee(satoshis_to_btc(fee))\n\n                movements.append(AssetMovement(\n                    location=Location.BITMEX,\n                    category=transaction_type,\n                    address=deserialize_asset_movement_address(movement, 'address', asset),\n                    transaction_id=get_key_if_has_val(movement, 'tx'),\n                    timestamp=timestamp,\n                    asset=asset,\n                    amount=amount,\n                    fee_asset=asset,\n                    fee=fee,\n                    link=str(movement['transactID']),\n                ))\n            except UnknownAsset as e:\n                self.msg_aggregator.add_warning(\n                    f'Found bitmex deposit/withdrawal with unknown asset '\n                    f'{e.asset_name}. Ignoring it.',\n                )\n                continue\n            except (DeserializationError, KeyError) as e:\n                msg = str(e)\n                if isinstance(e, KeyError):\n                    msg = f'Missing key entry for {msg}.'\n                self.msg_aggregator.add_error(\n                    'Unexpected data encountered during deserialization of a bitmex '\n                    'asset movement. Check logs for details and open a bug report.',\n                )\n                log.error(\n                    f'Unexpected data encountered during deserialization of bitmex '\n                    f'asset_movement {movement}. Error was: {msg}',\n                )\n                continue\n        return movements\n", "path": "rotkehlchen/exchanges/bitmex.py"}]}