# -*- coding: utf-8 -*-

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

from ccxt.base.exchange import Exchange
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.decimal_to_precision import TICK_SIZE


class bitmex(Exchange):

    def describe(self):
        return self.deep_extend(super(bitmex, self).describe(), {
            'id': 'bitmex',
            'name': 'BitMEX',
            'countries': ['SC'],  # Seychelles
            'version': 'v1',
            'userAgent': None,
            'rateLimit': 2000,
            'pro': True,
            'has': {
                'cancelAllOrders': True,
                'cancelOrder': True,
                'CORS': False,
                'createOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchLedger': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTransactions': 'emulated',
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '1h': '1h',
                '1d': '1d',
            },
            'urls': {
                'test': {
                    'public': 'https://testnet.bitmex.com',
                    'private': 'https://testnet.bitmex.com',
                },
                'logo': 'https://user-images.githubusercontent.com/1294454/27766319-f653c6e6-5ed4-11e7-933d-f0bc3699ae8f.jpg',
                'api': {
                    'public': 'https://www.bitmex.com',
                    'private': 'https://www.bitmex.com',
                },
                'www': 'https://www.bitmex.com',
                'doc': [
                    'https://www.bitmex.com/app/apiOverview',
                    'https://github.com/BitMEX/api-connectors/tree/master/official-http',
                ],
                'fees': 'https://www.bitmex.com/app/fees',
                'referral': 'https://www.bitmex.com/register/upZpOX',
            },
            'api': {
                'public': {
                    'get': [
                        'announcement',
                        'announcement/urgent',
                        'funding',
                        'instrument',
                        'instrument/active',
                        'instrument/activeAndIndices',
                        'instrument/activeIntervals',
                        'instrument/compositeIndex',
                        'instrument/indices',
                        'insurance',
                        'leaderboard',
                        'liquidation',
                        'orderBook',
                        'orderBook/L2',
                        'quote',
                        'quote/bucketed',
                        'schema',
                        'schema/websocketHelp',
                        'settlement',
                        'stats',
                        'stats/history',
                        'trade',
                        'trade/bucketed',
                    ],
                },
                'private': {
                    'get': [
                        'apiKey',
                        'chat',
                        'chat/channels',
                        'chat/connected',
                        'execution',
                        'execution/tradeHistory',
                        'notification',
                        'order',
                        'position',
                        'user',
                        'user/affiliateStatus',
                        'user/checkReferralCode',
                        'user/commission',
                        'user/depositAddress',
                        'user/executionHistory',
                        'user/margin',
                        'user/minWithdrawalFee',
                        'user/wallet',
                        'user/walletHistory',
                        'user/walletSummary',
                    ],
                    'post': [
                        'apiKey',
                        'apiKey/disable',
                        'apiKey/enable',
                        'chat',
                        'order',
                        'order/bulk',
                        'order/cancelAllAfter',
                        'order/closePosition',
                        'position/isolate',
                        'position/leverage',
                        'position/riskLimit',
                        'position/transferMargin',
                        'user/cancelWithdrawal',
                        'user/confirmEmail',
                        'user/confirmEnableTFA',
                        'user/confirmWithdrawal',
                        'user/disableTFA',
                        'user/logout',
                        'user/logoutAll',
                        'user/preferences',
                        'user/requestEnableTFA',
                        'user/requestWithdrawal',
                    ],
                    'put': [
                        'order',
                        'order/bulk',
                        'user',
                    ],
                    'delete': [
                        'apiKey',
                        'order',
                        'order/all',
                    ],
                },
            },
            'exceptions': {
                'exact': {
                    'Invalid API Key.': AuthenticationError,
                    'This key is disabled.': PermissionDenied,
                    'Access Denied': PermissionDenied,
                    'Duplicate clOrdID': InvalidOrder,
                    'orderQty is invalid': InvalidOrder,
                    'Invalid price': InvalidOrder,
                    'Invalid stopPx for ordType': InvalidOrder,
                },
                'broad': {
                    'Signature not valid': AuthenticationError,
                    'overloaded': ExchangeNotAvailable,
                    'Account has insufficient Available Balance': InsufficientFunds,
                    'Service unavailable': ExchangeNotAvailable,  # {"error":{"message":"Service unavailable","name":"HTTPError"}}
                    'Server Error': ExchangeError,  # {"error":{"message":"Server Error","name":"HTTPError"}}
                },
            },
            'precisionMode': TICK_SIZE,
            'options': {
                # https://blog.bitmex.com/api_announcement/deprecation-of-api-nonce-header/
                # https://github.com/ccxt/ccxt/issues/4789
                'api-expires': 5,  # in seconds
                'fetchOHLCVOpenTimestamp': True,
            },
        })

    def fetch_markets(self, params={}):
        response = self.publicGetInstrumentActiveAndIndices(params)
        result = []
        for i in range(0, len(response)):
            market = response[i]
            active = (market['state'] != 'Unlisted')
            id = market['symbol']
            baseId = market['underlying']
            quoteId = market['quoteCurrency']
            basequote = baseId + quoteId
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            swap = (id == basequote)
            # 'positionCurrency' may be empty("", as Bitmex currently returns for ETHUSD)
            # so let's take the quote currency first and then adjust if needed
            positionId = self.safe_string_2(market, 'positionCurrency', 'quoteCurrency')
            type = None
            future = False
            prediction = False
            position = self.safe_currency_code(positionId)
            symbol = id
            if swap:
                type = 'swap'
                symbol = base + '/' + quote
            elif id.find('B_') >= 0:
                prediction = True
                type = 'prediction'
            else:
                future = True
                type = 'future'
            precision = {
                'amount': None,
                'price': None,
            }
            lotSize = self.safe_float(market, 'lotSize')
            tickSize = self.safe_float(market, 'tickSize')
            if lotSize is not None:
                precision['amount'] = lotSize
            if tickSize is not None:
                precision['price'] = tickSize
            limits = {
                'amount': {
                    'min': None,
                    'max': None,
                },
                'price': {
                    'min': tickSize,
                    'max': self.safe_float(market, 'maxPrice'),
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
            }
            limitField = 'cost' if (position == quote) else 'amount'
            limits[limitField] = {
                'min': lotSize,
                'max': self.safe_float(market, 'maxOrderQty'),
            }
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': active,
                'precision': precision,
                'limits': limits,
                'taker': self.safe_float(market, 'takerFee'),
                'maker': self.safe_float(market, 'makerFee'),
                'type': type,
                'spot': False,
                'swap': swap,
                'future': future,
                'prediction': prediction,
                'info': market,
            })
        return result

    def parse_balance_response(self, response):
        #
        #     [
        #         {
        #             "account":1455728,
        #             "currency":"XBt",
        #             "riskLimit":1000000000000,
        #             "prevState":"",
        #             "state":"",
        #             "action":"",
        #             "amount":263542,
        #             "pendingCredit":0,
        #             "pendingDebit":0,
        #             "confirmedDebit":0,
        #             "prevRealisedPnl":0,
        #             "prevUnrealisedPnl":0,
        #             "grossComm":0,
        #             "grossOpenCost":0,
        #             "grossOpenPremium":0,
        #             "grossExecCost":0,
        #             "grossMarkValue":0,
        #             "riskValue":0,
        #             "taxableMargin":0,
        #             "initMargin":0,
        #             "maintMargin":0,
        #             "sessionMargin":0,
        #             "targetExcessMargin":0,
        #             "varMargin":0,
        #             "realisedPnl":0,
        #             "unrealisedPnl":0,
        #             "indicativeTax":0,
        #             "unrealisedProfit":0,
        #             "syntheticMargin":null,
        #             "walletBalance":263542,
        #             "marginBalance":263542,
        #             "marginBalancePcnt":1,
        #             "marginLeverage":0,
        #             "marginUsedPcnt":0,
        #             "excessMargin":263542,
        #             "excessMarginPcnt":1,
        #             "availableMargin":263542,
        #             "withdrawableMargin":263542,
        #             "timestamp":"2020-08-03T12:01:01.246Z",
        #             "grossLastValue":0,
        #             "commission":null
        #         }
        #     ]
        #
        result = {'info': response}
        for i in range(0, len(response)):
            balance = response[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            free = self.safe_float(balance, 'availableMargin')
            total = self.safe_float(balance, 'marginBalance')
            if code == 'BTC':
                if free is not None:
                    free /= 100000000
                if total is not None:
                    total /= 100000000
            account['free'] = free
            account['total'] = total
            result[code] = account
        return self.parse_balance(result)

    def fetch_balance(self, params={}):
        self.load_markets()
        request = {
            'currency': 'all',
        }
        response = self.privateGetUserMargin(self.extend(request, params))
        #
        #     [
        #         {
        #             "account":1455728,
        #             "currency":"XBt",
        #             "riskLimit":1000000000000,
        #             "prevState":"",
        #             "state":"",
        #             "action":"",
        #             "amount":263542,
        #             "pendingCredit":0,
        #             "pendingDebit":0,
        #             "confirmedDebit":0,
        #             "prevRealisedPnl":0,
        #             "prevUnrealisedPnl":0,
        #             "grossComm":0,
        #             "grossOpenCost":0,
        #             "grossOpenPremium":0,
        #             "grossExecCost":0,
        #             "grossMarkValue":0,
        #             "riskValue":0,
        #             "taxableMargin":0,
        #             "initMargin":0,
        #             "maintMargin":0,
        #             "sessionMargin":0,
        #             "targetExcessMargin":0,
        #             "varMargin":0,
        #             "realisedPnl":0,
        #             "unrealisedPnl":0,
        #             "indicativeTax":0,
        #             "unrealisedProfit":0,
        #             "syntheticMargin":null,
        #             "walletBalance":263542,
        #             "marginBalance":263542,
        #             "marginBalancePcnt":1,
        #             "marginLeverage":0,
        #             "marginUsedPcnt":0,
        #             "excessMargin":263542,
        #             "excessMarginPcnt":1,
        #             "availableMargin":263542,
        #             "withdrawableMargin":263542,
        #             "timestamp":"2020-08-03T12:01:01.246Z",
        #             "grossLastValue":0,
        #             "commission":null
        #         }
        #     ]
        #
        return self.parse_balance_response(response)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['depth'] = limit
        response = self.publicGetOrderBookL2(self.extend(request, params))
        result = {
            'bids': [],
            'asks': [],
            'timestamp': None,
            'datetime': None,
            'nonce': None,
        }
        for i in range(0, len(response)):
            order = response[i]
            side = 'asks' if (order['side'] == 'Sell') else 'bids'
            amount = self.safe_float(order, 'size')
            price = self.safe_float(order, 'price')
            # https://github.com/ccxt/ccxt/issues/4926
            # https://github.com/ccxt/ccxt/issues/4927
            # the exchange sometimes returns null price in the orderbook
            if price is not None:
                result[side].append([price, amount])
        result['bids'] = self.sort_by(result['bids'], 0, True)
        result['asks'] = self.sort_by(result['asks'], 0)
        return result

    def fetch_order(self, id, symbol=None, params={}):
        filter = {
            'filter': {
                'orderID': id,
            },
        }
        response = self.fetch_orders(symbol, None, None, self.deep_extend(filter, params))
        numResults = len(response)
        if numResults == 1:
            return response[0]
        raise OrderNotFound(self.id + ': The order ' + id + ' not found.')

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if since is not None:
            request['startTime'] = self.iso8601(since)
        if limit is not None:
            request['count'] = limit
        request = self.deep_extend(request, params)
        # why the hassle? urlencode in python is kinda broken for nested dicts.
        # E.g. self.urlencode({"filter": {"open": True}}) will return "filter={'open':+True}"
        # Bitmex doesn't like that. Hence resorting to self hack.
        if 'filter' in request:
            request['filter'] = self.json(request['filter'])
        response = self.privateGetOrder(request)
        return self.parse_orders(response, market, since, limit)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'filter': {
                'open': True,
            },
        }
        return self.fetch_orders(symbol, since, limit, self.deep_extend(request, params))

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        # Bitmex barfs if you set 'open': False in the filter...
        orders = self.fetch_orders(symbol, since, limit, params)
        return self.filter_by(orders, 'status', 'closed')

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if since is not None:
            request['startTime'] = self.iso8601(since)
        if limit is not None:
            request['count'] = limit
        request = self.deep_extend(request, params)
        # why the hassle? urlencode in python is kinda broken for nested dicts.
        # E.g. self.urlencode({"filter": {"open": True}}) will return "filter={'open':+True}"
        # Bitmex doesn't like that. Hence resorting to self hack.
        if 'filter' in request:
            request['filter'] = self.json(request['filter'])
        response = self.privateGetExecutionTradeHistory(request)
        #
        #     [
        #         {
        #             "execID": "string",
        #             "orderID": "string",
        #             "clOrdID": "string",
        #             "clOrdLinkID": "string",
        #             "account": 0,
        #             "symbol": "string",
        #             "side": "string",
        #             "lastQty": 0,
        #             "lastPx": 0,
        #             "underlyingLastPx": 0,
        #             "lastMkt": "string",
        #             "lastLiquidityInd": "string",
        #             "simpleOrderQty": 0,
        #             "orderQty": 0,
        #             "price": 0,
        #             "displayQty": 0,
        #             "stopPx": 0,
        #             "pegOffsetValue": 0,
        #             "pegPriceType": "string",
        #             "currency": "string",
        #             "settlCurrency": "string",
        #             "execType": "string",
        #             "ordType": "string",
        #             "timeInForce": "string",
        #             "execInst": "string",
        #             "contingencyType": "string",
        #             "exDestination": "string",
        #             "ordStatus": "string",
        #             "triggered": "string",
        #             "workingIndicator": True,
        #             "ordRejReason": "string",
        #             "simpleLeavesQty": 0,
        #             "leavesQty": 0,
        #             "simpleCumQty": 0,
        #             "cumQty": 0,
        #             "avgPx": 0,
        #             "commission": 0,
        #             "tradePublishIndicator": "string",
        #             "multiLegReportingType": "string",
        #             "text": "string",
        #             "trdMatchID": "string",
        #             "execCost": 0,
        #             "execComm": 0,
        #             "homeNotional": 0,
        #             "foreignNotional": 0,
        #             "transactTime": "2019-03-05T12:47:02.762Z",
        #             "timestamp": "2019-03-05T12:47:02.762Z"
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_ledger_entry_type(self, type):
        types = {
            'Withdrawal': 'transaction',
            'RealisedPNL': 'margin',
            'UnrealisedPNL': 'margin',
            'Deposit': 'transaction',
            'Transfer': 'transfer',
            'AffiliatePayout': 'referral',
        }
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item, currency=None):
        #
        #     {
        #         transactID: "69573da3-7744-5467-3207-89fd6efe7a47",
        #         account:  24321,
        #         currency: "XBt",
        #         transactType: "Withdrawal",  # "AffiliatePayout", "Transfer", "Deposit", "RealisedPNL", ...
        #         amount:  -1000000,
        #         fee:  300000,
        #         transactStatus: "Completed",  # "Canceled", ...
        #         address: "1Ex4fkF4NhQaQdRWNoYpqiPbDBbq18Kdd9",
        #         tx: "3BMEX91ZhhKoWtsH9QRb5dNXnmnGpiEetA",
        #         text: "",
        #         transactTime: "2017-03-21T20:05:14.388Z",
        #         walletBalance:  0,  # balance after
        #         marginBalance:  null,
        #         timestamp: "2017-03-22T13:09:23.514Z"
        #     }
        #
        # ButMEX returns the unrealized pnl from the wallet history endpoint.
        # The unrealized pnl transaction has an empty timestamp.
        # It is not related to historical pnl it has status set to "Pending".
        # Therefore it's not a part of the history at all.
        # https://github.com/ccxt/ccxt/issues/6047
        #
        #     {
        #         "transactID":"00000000-0000-0000-0000-000000000000",
        #         "account":121210,
        #         "currency":"XBt",
        #         "transactType":"UnrealisedPNL",
        #         "amount":-5508,
        #         "fee":0,
        #         "transactStatus":"Pending",
        #         "address":"XBTUSD",
        #         "tx":"",
        #         "text":"",
        #         "transactTime":null,  # ←---------------------------- null
        #         "walletBalance":139198767,
        #         "marginBalance":139193259,
        #         "timestamp":null  # ←---------------------------- null
        #     }
        #
        id = self.safe_string(item, 'transactID')
        account = self.safe_string(item, 'account')
        referenceId = self.safe_string(item, 'tx')
        referenceAccount = None
        type = self.parse_ledger_entry_type(self.safe_string(item, 'transactType'))
        currencyId = self.safe_string(item, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        amount = self.safe_float(item, 'amount')
        if amount is not None:
            amount = amount / 100000000
        timestamp = self.parse8601(self.safe_string(item, 'transactTime'))
        if timestamp is None:
            # https://github.com/ccxt/ccxt/issues/6047
            # set the timestamp to zero, 1970 Jan 1 00:00:00
            # for unrealized pnl and other transactions without a timestamp
            timestamp = 0  # see comments above
        feeCost = self.safe_float(item, 'fee', 0)
        if feeCost is not None:
            feeCost = feeCost / 100000000
        fee = {
            'cost': feeCost,
            'currency': code,
        }
        after = self.safe_float(item, 'walletBalance')
        if after is not None:
            after = after / 100000000
        before = self.sum(after, -amount)
        direction = None
        if amount < 0:
            direction = 'out'
            amount = abs(amount)
        else:
            direction = 'in'
        status = self.parse_transaction_status(self.safe_string(item, 'transactStatus'))
        return {
            'id': id,
            'info': item,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'direction': direction,
            'account': account,
            'referenceId': referenceId,
            'referenceAccount': referenceAccount,
            'type': type,
            'currency': code,
            'amount': amount,
            'before': before,
            'after': after,
            'status': status,
            'fee': fee,
        }

    def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        currency = None
        if code is not None:
            currency = self.currency(code)
        request = {
            # 'start': 123,
        }
        #
        #     if since is not None:
        #         # date-based pagination not supported
        #     }
        #
        if limit is not None:
            request['count'] = limit
        response = self.privateGetUserWalletHistory(self.extend(request, params))
        #
        #     [
        #         {
        #             transactID: "69573da3-7744-5467-3207-89fd6efe7a47",
        #             account:  24321,
        #             currency: "XBt",
        #             transactType: "Withdrawal",  # "AffiliatePayout", "Transfer", "Deposit", "RealisedPNL", ...
        #             amount:  -1000000,
        #             fee:  300000,
        #             transactStatus: "Completed",  # "Canceled", ...
        #             address: "1Ex4fkF4NhQaQdRWNoYpqiPbDBbq18Kdd9",
        #             tx: "3BMEX91ZhhKoWtsH9QRb5dNXnmnGpiEetA",
        #             text: "",
        #             transactTime: "2017-03-21T20:05:14.388Z",
        #             walletBalance:  0,  # balance after
        #             marginBalance:  null,
        #             timestamp: "2017-03-22T13:09:23.514Z"
        #         }
        #     ]
        #
        return self.parse_ledger(response, currency, since, limit)

    def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'start': 123,
        }
        #
        #     if since is not None:
        #         # date-based pagination not supported
        #     }
        #
        if limit is not None:
            request['count'] = limit
        response = self.privateGetUserWalletHistory(self.extend(request, params))
        transactions = self.filter_by_array(response, 'transactType', ['Withdrawal', 'Deposit'], False)
        currency = None
        if code is not None:
            currency = self.currency(code)
        return self.parse_transactions(transactions, currency, since, limit)

    def parse_transaction_status(self, status):
        statuses = {
            'Canceled': 'canceled',
            'Completed': 'ok',
            'Pending': 'pending',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        #   {
        #      'transactID': 'ffe699c2-95ee-4c13-91f9-0faf41daec25',
        #      'account': 123456,
        #      'currency': 'XBt',
        #      'transactType': 'Withdrawal',
        #      'amount': -100100000,
        #      'fee': 100000,
        #      'transactStatus': 'Completed',
        #      'address': '385cR5DM96n1HvBDMzLHPYcw89fZAXULJP',
        #      'tx': '3BMEXabcdefghijklmnopqrstuvwxyz123',
        #      'text': '',
        #      'transactTime': '2019-01-02T01:00:00.000Z',
        #      'walletBalance': 99900000,
        #      'marginBalance': None,
        #      'timestamp': '2019-01-02T13:00:00.000Z'
        #   }
        #
        id = self.safe_string(transaction, 'transactID')
        # For deposits, transactTime == timestamp
        # For withdrawals, transactTime is submission, timestamp is processed
        transactTime = self.parse8601(self.safe_string(transaction, 'transactTime'))
        timestamp = self.parse8601(self.safe_string(transaction, 'timestamp'))
        type = self.safe_string_lower(transaction, 'transactType')
        # Deposits have no from address or to address, withdrawals have both
        address = None
        addressFrom = None
        addressTo = None
        if type == 'withdrawal':
            address = self.safe_string(transaction, 'address')
            addressFrom = self.safe_string(transaction, 'tx')
            addressTo = address
        amount = self.safe_integer(transaction, 'amount')
        if amount is not None:
            amount = abs(amount) / 10000000
        feeCost = self.safe_integer(transaction, 'fee')
        if feeCost is not None:
            feeCost = feeCost / 10000000
        fee = {
            'cost': feeCost,
            'currency': 'BTC',
        }
        status = self.safe_string(transaction, 'transactStatus')
        if status is not None:
            status = self.parse_transaction_status(status)
        return {
            'info': transaction,
            'id': id,
            'txid': None,
            'timestamp': transactTime,
            'datetime': self.iso8601(transactTime),
            'addressFrom': addressFrom,
            'address': address,
            'addressTo': addressTo,
            'tagFrom': None,
            'tag': None,
            'tagTo': None,
            'type': type,
            'amount': amount,
            # BTC is the only currency on Bitmex
            'currency': 'BTC',
            'status': status,
            'updated': timestamp,
            'comment': None,
            'fee': fee,
        }

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        if not market['active']:
            raise ExchangeError(self.id + ': symbol ' + symbol + ' is delisted')
        tickers = self.fetch_tickers([symbol], params)
        ticker = self.safe_value(tickers, symbol)
        if ticker is None:
            raise ExchangeError(self.id + ' ticker symbol ' + symbol + ' not found')
        return ticker

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        response = self.publicGetInstrumentActiveAndIndices(params)
        result = {}
        for i in range(0, len(response)):
            ticker = self.parse_ticker(response[i])
            symbol = self.safe_string(ticker, 'symbol')
            if symbol is not None:
                result[symbol] = ticker
        return self.filter_by_array(result, 'symbol', symbols)

    def parse_ticker(self, ticker, market=None):
        #
        #     {                        symbol: "ETHH19",
        #                           rootSymbol: "ETH",
        #                                state: "Open",
        #                                  typ: "FFCCSX",
        #                              listing: "2018-12-17T04:00:00.000Z",
        #                                front: "2019-02-22T12:00:00.000Z",
        #                               expiry: "2019-03-29T12:00:00.000Z",
        #                               settle: "2019-03-29T12:00:00.000Z",
        #                       relistInterval:  null,
        #                           inverseLeg: "",
        #                              sellLeg: "",
        #                               buyLeg: "",
        #                     optionStrikePcnt:  null,
        #                    optionStrikeRound:  null,
        #                    optionStrikePrice:  null,
        #                     optionMultiplier:  null,
        #                     positionCurrency: "ETH",
        #                           underlying: "ETH",
        #                        quoteCurrency: "XBT",
        #                     underlyingSymbol: "ETHXBT=",
        #                            reference: "BMEX",
        #                      referenceSymbol: ".BETHXBT30M",
        #                         calcInterval:  null,
        #                      publishInterval:  null,
        #                          publishTime:  null,
        #                          maxOrderQty:  100000000,
        #                             maxPrice:  10,
        #                              lotSize:  1,
        #                             tickSize:  0.00001,
        #                           multiplier:  100000000,
        #                        settlCurrency: "XBt",
        #       underlyingToPositionMultiplier:  1,
        #         underlyingToSettleMultiplier:  null,
        #              quoteToSettleMultiplier:  100000000,
        #                             isQuanto:  False,
        #                            isInverse:  False,
        #                           initMargin:  0.02,
        #                          maintMargin:  0.01,
        #                            riskLimit:  5000000000,
        #                             riskStep:  5000000000,
        #                                limit:  null,
        #                               capped:  False,
        #                                taxed:  True,
        #                           deleverage:  True,
        #                             makerFee:  -0.0005,
        #                             takerFee:  0.0025,
        #                        settlementFee:  0,
        #                         insuranceFee:  0,
        #                    fundingBaseSymbol: "",
        #                   fundingQuoteSymbol: "",
        #                 fundingPremiumSymbol: "",
        #                     fundingTimestamp:  null,
        #                      fundingInterval:  null,
        #                          fundingRate:  null,
        #                indicativeFundingRate:  null,
        #                   rebalanceTimestamp:  null,
        #                    rebalanceInterval:  null,
        #                     openingTimestamp: "2019-02-13T08:00:00.000Z",
        #                     closingTimestamp: "2019-02-13T09:00:00.000Z",
        #                      sessionInterval: "2000-01-01T01:00:00.000Z",
        #                       prevClosePrice:  0.03347,
        #                       limitDownPrice:  null,
        #                         limitUpPrice:  null,
        #               bankruptLimitDownPrice:  null,
        #                 bankruptLimitUpPrice:  null,
        #                      prevTotalVolume:  1386531,
        #                          totalVolume:  1387062,
        #                               volume:  531,
        #                            volume24h:  17118,
        #                    prevTotalTurnover:  4741294246000,
        #                        totalTurnover:  4743103466000,
        #                             turnover:  1809220000,
        #                          turnover24h:  57919845000,
        #                      homeNotional24h:  17118,
        #                   foreignNotional24h:  579.19845,
        #                         prevPrice24h:  0.03349,
        #                                 vwap:  0.03383564,
        #                            highPrice:  0.03458,
        #                             lowPrice:  0.03329,
        #                            lastPrice:  0.03406,
        #                   lastPriceProtected:  0.03406,
        #                    lastTickDirection: "ZeroMinusTick",
        #                       lastChangePcnt:  0.017,
        #                             bidPrice:  0.03406,
        #                             midPrice:  0.034065,
        #                             askPrice:  0.03407,
        #                       impactBidPrice:  0.03406,
        #                       impactMidPrice:  0.034065,
        #                       impactAskPrice:  0.03407,
        #                         hasLiquidity:  True,
        #                         openInterest:  83679,
        #                            openValue:  285010674000,
        #                           fairMethod: "ImpactMidPrice",
        #                        fairBasisRate:  0,
        #                            fairBasis:  0,
        #                            fairPrice:  0.03406,
        #                           markMethod: "FairPrice",
        #                            markPrice:  0.03406,
        #                    indicativeTaxRate:  0,
        #                indicativeSettlePrice:  0.03406,
        #                optionUnderlyingPrice:  null,
        #                         settledPrice:  null,
        #                            timestamp: "2019-02-13T08:40:30.000Z",
        #     }
        #
        symbol = None
        marketId = self.safe_string(ticker, 'symbol')
        market = self.safe_value(self.markets_by_id, marketId, market)
        if market is not None:
            symbol = market['symbol']
        timestamp = self.parse8601(self.safe_string(ticker, 'timestamp'))
        open = self.safe_float(ticker, 'prevPrice24h')
        last = self.safe_float(ticker, 'lastPrice')
        change = None
        percentage = None
        if last is not None and open is not None:
            change = last - open
            if open > 0:
                percentage = change / open * 100
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'highPrice'),
            'low': self.safe_float(ticker, 'lowPrice'),
            'bid': self.safe_float(ticker, 'bidPrice'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'askPrice'),
            'askVolume': None,
            'vwap': self.safe_float(ticker, 'vwap'),
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': self.sum(open, last) / 2,
            'baseVolume': self.safe_float(ticker, 'homeNotional24h'),
            'quoteVolume': self.safe_float(ticker, 'foreignNotional24h'),
            'info': ticker,
        }

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "timestamp":"2015-09-25T13:38:00.000Z",
        #         "symbol":"XBTUSD",
        #         "open":237.45,
        #         "high":237.45,
        #         "low":237.45,
        #         "close":237.45,
        #         "trades":0,
        #         "volume":0,
        #         "vwap":null,
        #         "lastSize":null,
        #         "turnover":0,
        #         "homeNotional":0,
        #         "foreignNotional":0
        #     }
        #
        return [
            self.parse8601(self.safe_string(ohlcv, 'timestamp')),
            self.safe_float(ohlcv, 'open'),
            self.safe_float(ohlcv, 'high'),
            self.safe_float(ohlcv, 'low'),
            self.safe_float(ohlcv, 'close'),
            self.safe_float(ohlcv, 'volume'),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        # send JSON key/value pairs, such as {"key": "value"}
        # filter by individual fields and do advanced queries on timestamps
        # filter = {'key': 'value'}
        # send a bare series(e.g. XBU) to nearest expiring contract in that series
        # you can also send a timeframe, e.g. XBU:monthly
        # timeframes: daily, weekly, monthly, quarterly, and biquarterly
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'binSize': self.timeframes[timeframe],
            'partial': True,     # True == include yet-incomplete current bins
            # 'filter': filter,  # filter by individual fields and do advanced queries
            # 'columns': [],    # will return all columns if omitted
            # 'start': 0,       # starting point for results(wtf?)
            # 'reverse': False,  # True == newest first
            # 'endTime': '',    # ending date filter for results
        }
        if limit is not None:
            request['count'] = limit  # default 100, max 500
        duration = self.parse_timeframe(timeframe) * 1000
        fetchOHLCVOpenTimestamp = self.safe_value(self.options, 'fetchOHLCVOpenTimestamp', True)
        # if since is not set, they will return candles starting from 2017-01-01
        if since is not None:
            timestamp = since
            if fetchOHLCVOpenTimestamp:
                timestamp = self.sum(timestamp, duration)
            ymdhms = self.ymdhms(timestamp)
            request['startTime'] = ymdhms  # starting date filter for results
        else:
            request['reverse'] = True
        response = self.publicGetTradeBucketed(self.extend(request, params))
        #
        #     [
        #         {"timestamp":"2015-09-25T13:38:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0},
        #         {"timestamp":"2015-09-25T13:39:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0},
        #         {"timestamp":"2015-09-25T13:40:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0}
        #     ]
        #
        result = self.parse_ohlcvs(response, market, timeframe, since, limit)
        if fetchOHLCVOpenTimestamp:
            # bitmex returns the candle's close timestamp - https://github.com/ccxt/ccxt/issues/4446
            # we can emulate the open timestamp by shifting all the timestamps one place
            # so the previous close becomes the current open, and we drop the first candle
            for i in range(0, len(result)):
                result[i][0] = result[i][0] - duration
        return result

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         timestamp: '2018-08-28T00:00:02.735Z',
        #         symbol: 'XBTUSD',
        #         side: 'Buy',
        #         size: 2000,
        #         price: 6906.5,
        #         tickDirection: 'PlusTick',
        #         trdMatchID: 'b9a42432-0a46-6a2f-5ecc-c32e9ca4baf8',
        #         grossValue: 28958000,
        #         homeNotional: 0.28958,
        #         foreignNotional: 2000
        #     }
        #
        # fetchMyTrades(private)
        #
        #     {
        #         "execID": "string",
        #         "orderID": "string",
        #         "clOrdID": "string",
        #         "clOrdLinkID": "string",
        #         "account": 0,
        #         "symbol": "string",
        #         "side": "string",
        #         "lastQty": 0,
        #         "lastPx": 0,
        #         "underlyingLastPx": 0,
        #         "lastMkt": "string",
        #         "lastLiquidityInd": "string",
        #         "simpleOrderQty": 0,
        #         "orderQty": 0,
        #         "price": 0,
        #         "displayQty": 0,
        #         "stopPx": 0,
        #         "pegOffsetValue": 0,
        #         "pegPriceType": "string",
        #         "currency": "string",
        #         "settlCurrency": "string",
        #         "execType": "string",
        #         "ordType": "string",
        #         "timeInForce": "string",
        #         "execInst": "string",
        #         "contingencyType": "string",
        #         "exDestination": "string",
        #         "ordStatus": "string",
        #         "triggered": "string",
        #         "workingIndicator": True,
        #         "ordRejReason": "string",
        #         "simpleLeavesQty": 0,
        #         "leavesQty": 0,
        #         "simpleCumQty": 0,
        #         "cumQty": 0,
        #         "avgPx": 0,
        #         "commission": 0,
        #         "tradePublishIndicator": "string",
        #         "multiLegReportingType": "string",
        #         "text": "string",
        #         "trdMatchID": "string",
        #         "execCost": 0,
        #         "execComm": 0,
        #         "homeNotional": 0,
        #         "foreignNotional": 0,
        #         "transactTime": "2019-03-05T12:47:02.762Z",
        #         "timestamp": "2019-03-05T12:47:02.762Z"
        #     }
        #
        timestamp = self.parse8601(self.safe_string(trade, 'timestamp'))
        price = self.safe_float_2(trade, 'avgPx', 'price')
        amount = self.safe_float_2(trade, 'size', 'lastQty')
        id = self.safe_string(trade, 'trdMatchID')
        order = self.safe_string(trade, 'orderID')
        side = self.safe_string_lower(trade, 'side')
        # price * amount doesn't work for all symbols(e.g. XBT, ETH)
        cost = self.safe_float(trade, 'execCost')
        if cost is not None:
            cost = abs(cost) / 100000000
        fee = None
        if 'execComm' in trade:
            feeCost = self.safe_float(trade, 'execComm')
            feeCost = feeCost / 100000000
            currencyId = self.safe_string(trade, 'settlCurrency')
            feeCurrency = self.safe_currency_code(currencyId)
            feeRate = self.safe_float(trade, 'commission')
            fee = {
                'cost': feeCost,
                'currency': feeCurrency,
                'rate': feeRate,
            }
        takerOrMaker = None
        if fee is not None:
            takerOrMaker = 'maker' if (fee['cost'] < 0) else 'taker'
        marketId = self.safe_string(trade, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        type = self.safe_string_lower(trade, 'ordType')
        return {
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': order,
            'type': type,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': price,
            'cost': cost,
            'amount': amount,
            'fee': fee,
        }

    def parse_order_status(self, status):
        statuses = {
            'New': 'open',
            'PartiallyFilled': 'open',
            'Filled': 'closed',
            'DoneForDay': 'open',
            'Canceled': 'canceled',
            'PendingCancel': 'open',
            'PendingNew': 'open',
            'Rejected': 'rejected',
            'Expired': 'expired',
            'Stopped': 'open',
            'Untriggered': 'open',
            'Triggered': 'open',
        }
        return self.safe_string(statuses, status, status)

    def parse_time_in_force(self, timeInForce):
        timeInForces = {
            'Day': 'Day',
            'GoodTillCancel': 'GTC',
            'ImmediateOrCancel': 'IOC',
            'FillOrKill': 'FOK',
        }
        return self.safe_string(timeInForces, timeInForce, timeInForce)

    def parse_order(self, order, market=None):
        #
        #     {
        #         "orderID":"56222c7a-9956-413a-82cf-99f4812c214b",
        #         "clOrdID":"",
        #         "clOrdLinkID":"",
        #         "account":1455728,
        #         "symbol":"XBTUSD",
        #         "side":"Sell",
        #         "simpleOrderQty":null,
        #         "orderQty":1,
        #         "price":40000,
        #         "displayQty":null,
        #         "stopPx":null,
        #         "pegOffsetValue":null,
        #         "pegPriceType":"",
        #         "currency":"USD",
        #         "settlCurrency":"XBt",
        #         "ordType":"Limit",
        #         "timeInForce":"GoodTillCancel",
        #         "execInst":"",
        #         "contingencyType":"",
        #         "exDestination":"XBME",
        #         "ordStatus":"New",
        #         "triggered":"",
        #         "workingIndicator":true,
        #         "ordRejReason":"",
        #         "simpleLeavesQty":null,
        #         "leavesQty":1,
        #         "simpleCumQty":null,
        #         "cumQty":0,
        #         "avgPx":null,
        #         "multiLegReportingType":"SingleSecurity",
        #         "text":"Submitted via API.",
        #         "transactTime":"2021-01-02T21:38:49.246Z",
        #         "timestamp":"2021-01-02T21:38:49.246Z"
        #     }
        #
        status = self.parse_order_status(self.safe_string(order, 'ordStatus'))
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.parse8601(self.safe_string(order, 'timestamp'))
        lastTradeTimestamp = self.parse8601(self.safe_string(order, 'transactTime'))
        price = self.safe_float(order, 'price')
        amount = self.safe_float(order, 'orderQty')
        filled = self.safe_float(order, 'cumQty', 0.0)
        remaining = None
        if amount is not None:
            if filled is not None:
                remaining = max(amount - filled, 0.0)
        average = self.safe_float(order, 'avgPx')
        cost = None
        if filled is not None:
            if average is not None:
                cost = average * filled
            elif price is not None:
                cost = price * filled
        id = self.safe_string(order, 'orderID')
        type = self.safe_string_lower(order, 'ordType')
        side = self.safe_string_lower(order, 'side')
        clientOrderId = self.safe_string(order, 'clOrdID')
        timeInForce = self.parse_time_in_force(self.safe_string(order, 'timeInForce'))
        stopPrice = self.safe_float(order, 'stopPx')
        execInst = self.safe_string(order, 'execInst')
        postOnly = (execInst == 'ParticipateDoNotInitiate')
        return {
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': None,
            'trades': None,
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if since is not None:
            request['startTime'] = self.iso8601(since)
        else:
            # by default reverse=false, i.e. trades are fetched since the time of market inception(year 2015 for XBTUSD)
            request['reverse'] = True
        if limit is not None:
            request['count'] = limit
        response = self.publicGetTrade(self.extend(request, params))
        #
        #     [
        #         {
        #             timestamp: '2018-08-28T00:00:02.735Z',
        #             symbol: 'XBTUSD',
        #             side: 'Buy',
        #             size: 2000,
        #             price: 6906.5,
        #             tickDirection: 'PlusTick',
        #             trdMatchID: 'b9a42432-0a46-6a2f-5ecc-c32e9ca4baf8',
        #             grossValue: 28958000,
        #             homeNotional: 0.28958,
        #             foreignNotional: 2000
        #         },
        #         {
        #             timestamp: '2018-08-28T00:00:03.778Z',
        #             symbol: 'XBTUSD',
        #             side: 'Sell',
        #             size: 1000,
        #             price: 6906,
        #             tickDirection: 'MinusTick',
        #             trdMatchID: '0d4f1682-5270-a800-569b-4a0eb92db97c',
        #             grossValue: 14480000,
        #             homeNotional: 0.1448,
        #             foreignNotional: 1000
        #         },
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        orderType = self.capitalize(type)
        request = {
            'symbol': market['id'],
            'side': self.capitalize(side),
            'orderQty': float(self.amount_to_precision(symbol, amount)),
            'ordType': orderType,
        }
        if (orderType == 'Stop') or (orderType == 'StopLimit') or (orderType == 'MarketIfTouched') or (orderType == 'LimitIfTouched'):
            stopPrice = self.safe_float_2(params, 'stopPx', 'stopPrice')
            if stopPrice is None:
                raise ArgumentsRequired(self.id + ' createOrder() requires a stopPx or stopPrice parameter for the ' + orderType + ' order type')
            else:
                request['stopPx'] = float(self.price_to_precision(symbol, stopPrice))
                params = self.omit(params, ['stopPx', 'stopPrice'])
        if (orderType == 'Limit') or (orderType == 'StopLimit') or (orderType == 'LimitIfTouched'):
            request['price'] = float(self.price_to_precision(symbol, price))
        clientOrderId = self.safe_string_2(params, 'clOrdID', 'clientOrderId')
        if clientOrderId is not None:
            request['clOrdID'] = clientOrderId
            params = self.omit(params, ['clOrdID', 'clientOrderId'])
        response = self.privatePostOrder(self.extend(request, params))
        return self.parse_order(response, market)

    def edit_order(self, id, symbol, type, side, amount=None, price=None, params={}):
        self.load_markets()
        request = {}
        origClOrdID = self.safe_string_2(params, 'origClOrdID', 'clientOrderId')
        if origClOrdID is not None:
            request['origClOrdID'] = origClOrdID
            clientOrderId = self.safe_string(params, 'clOrdID', 'clientOrderId')
            if clientOrderId is not None:
                request['clOrdID'] = clientOrderId
            params = self.omit(params, ['origClOrdID', 'clOrdID', 'clientOrderId'])
        else:
            request['orderID'] = id
        if amount is not None:
            request['orderQty'] = amount
        if price is not None:
            request['price'] = price
        response = self.privatePutOrder(self.extend(request, params))
        return self.parse_order(response)

    def cancel_order(self, id, symbol=None, params={}):
        self.load_markets()
        # https://github.com/ccxt/ccxt/issues/6507
        clientOrderId = self.safe_string_2(params, 'clOrdID', 'clientOrderId')
        request = {}
        if clientOrderId is None:
            request['orderID'] = id
        else:
            request['clOrdID'] = clientOrderId
            params = self.omit(params, ['clOrdID', 'clientOrderId'])
        response = self.privateDeleteOrder(self.extend(request, params))
        order = self.safe_value(response, 0, {})
        error = self.safe_string(order, 'error')
        if error is not None:
            if error.find('Unable to cancel order due to existing state') >= 0:
                raise OrderNotFound(self.id + ' cancelOrder() failed: ' + error)
        return self.parse_order(order)

    def cancel_all_orders(self, symbol=None, params={}):
        self.load_markets()
        request = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        response = self.privateDeleteOrderAll(self.extend(request, params))
        #
        #     [
        #         {
        #             "orderID": "string",
        #             "clOrdID": "string",
        #             "clOrdLinkID": "string",
        #             "account": 0,
        #             "symbol": "string",
        #             "side": "string",
        #             "simpleOrderQty": 0,
        #             "orderQty": 0,
        #             "price": 0,
        #             "displayQty": 0,
        #             "stopPx": 0,
        #             "pegOffsetValue": 0,
        #             "pegPriceType": "string",
        #             "currency": "string",
        #             "settlCurrency": "string",
        #             "ordType": "string",
        #             "timeInForce": "string",
        #             "execInst": "string",
        #             "contingencyType": "string",
        #             "exDestination": "string",
        #             "ordStatus": "string",
        #             "triggered": "string",
        #             "workingIndicator": True,
        #             "ordRejReason": "string",
        #             "simpleLeavesQty": 0,
        #             "leavesQty": 0,
        #             "simpleCumQty": 0,
        #             "cumQty": 0,
        #             "avgPx": 0,
        #             "multiLegReportingType": "string",
        #             "text": "string",
        #             "transactTime": "2020-06-01T09:36:35.290Z",
        #             "timestamp": "2020-06-01T09:36:35.290Z"
        #         }
        #     ]
        #
        return self.parse_orders(response, market)

    def fetch_positions(self, symbols=None, since=None, limit=None, params={}):
        self.load_markets()
        response = self.privateGetPosition(params)
        #     [
        #         {
        #             "account": 0,
        #             "symbol": "string",
        #             "currency": "string",
        #             "underlying": "string",
        #             "quoteCurrency": "string",
        #             "commission": 0,
        #             "initMarginReq": 0,
        #             "maintMarginReq": 0,
        #             "riskLimit": 0,
        #             "leverage": 0,
        #             "crossMargin": True,
        #             "deleveragePercentile": 0,
        #             "rebalancedPnl": 0,
        #             "prevRealisedPnl": 0,
        #             "prevUnrealisedPnl": 0,
        #             "prevClosePrice": 0,
        #             "openingTimestamp": "2020-11-09T06:53:59.892Z",
        #             "openingQty": 0,
        #             "openingCost": 0,
        #             "openingComm": 0,
        #             "openOrderBuyQty": 0,
        #             "openOrderBuyCost": 0,
        #             "openOrderBuyPremium": 0,
        #             "openOrderSellQty": 0,
        #             "openOrderSellCost": 0,
        #             "openOrderSellPremium": 0,
        #             "execBuyQty": 0,
        #             "execBuyCost": 0,
        #             "execSellQty": 0,
        #             "execSellCost": 0,
        #             "execQty": 0,
        #             "execCost": 0,
        #             "execComm": 0,
        #             "currentTimestamp": "2020-11-09T06:53:59.893Z",
        #             "currentQty": 0,
        #             "currentCost": 0,
        #             "currentComm": 0,
        #             "realisedCost": 0,
        #             "unrealisedCost": 0,
        #             "grossOpenCost": 0,
        #             "grossOpenPremium": 0,
        #             "grossExecCost": 0,
        #             "isOpen": True,
        #             "markPrice": 0,
        #             "markValue": 0,
        #             "riskValue": 0,
        #             "homeNotional": 0,
        #             "foreignNotional": 0,
        #             "posState": "string",
        #             "posCost": 0,
        #             "posCost2": 0,
        #             "posCross": 0,
        #             "posInit": 0,
        #             "posComm": 0,
        #             "posLoss": 0,
        #             "posMargin": 0,
        #             "posMaint": 0,
        #             "posAllowance": 0,
        #             "taxableMargin": 0,
        #             "initMargin": 0,
        #             "maintMargin": 0,
        #             "sessionMargin": 0,
        #             "targetExcessMargin": 0,
        #             "varMargin": 0,
        #             "realisedGrossPnl": 0,
        #             "realisedTax": 0,
        #             "realisedPnl": 0,
        #             "unrealisedGrossPnl": 0,
        #             "longBankrupt": 0,
        #             "shortBankrupt": 0,
        #             "taxBase": 0,
        #             "indicativeTaxRate": 0,
        #             "indicativeTax": 0,
        #             "unrealisedTax": 0,
        #             "unrealisedPnl": 0,
        #             "unrealisedPnlPcnt": 0,
        #             "unrealisedRoePcnt": 0,
        #             "simpleQty": 0,
        #             "simpleCost": 0,
        #             "simpleValue": 0,
        #             "simplePnl": 0,
        #             "simplePnlPcnt": 0,
        #             "avgCostPrice": 0,
        #             "avgEntryPrice": 0,
        #             "breakEvenPrice": 0,
        #             "marginCallPrice": 0,
        #             "liquidationPrice": 0,
        #             "bankruptPrice": 0,
        #             "timestamp": "2020-11-09T06:53:59.894Z",
        #             "lastPrice": 0,
        #             "lastValue": 0
        #         }
        #     ]
        #
        # todo unify parsePosition/parsePositions
        return response

    def is_fiat(self, currency):
        if currency == 'EUR':
            return True
        if currency == 'PLN':
            return True
        return False

    def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        self.load_markets()
        # currency = self.currency(code)
        if code != 'BTC':
            raise ExchangeError(self.id + ' supoprts BTC withdrawals only, other currencies coming soon...')
        request = {
            'currency': 'XBt',  # temporarily
            'amount': amount,
            'address': address,
            # 'otpToken': '123456',  # requires if two-factor auth(OTP) is enabled
            # 'fee': 0.001,  # bitcoin network fee
        }
        response = self.privatePostUserRequestWithdrawal(self.extend(request, params))
        return {
            'info': response,
            'id': self.safe_string(response, 'transactID'),
        }

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return
        if code == 429:
            raise DDoSProtection(self.id + ' ' + body)
        if code >= 400:
            error = self.safe_value(response, 'error', {})
            message = self.safe_string(error, 'message')
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            if code == 400:
                raise BadRequest(feedback)
            raise ExchangeError(feedback)  # unknown message

    def nonce(self):
        return self.milliseconds()

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        query = '/api/' + self.version + '/' + path
        if method == 'GET':
            if params:
                query += '?' + self.urlencode(params)
        else:
            format = self.safe_string(params, '_format')
            if format is not None:
                query += '?' + self.urlencode({'_format': format})
                params = self.omit(params, '_format')
        url = self.urls['api'][api] + query
        if self.apiKey and self.secret:
            auth = method + query
            expires = self.safe_integer(self.options, 'api-expires')
            headers = {
                'Content-Type': 'application/json',
                'api-key': self.apiKey,
            }
            expires = self.sum(self.seconds(), expires)
            expires = str(expires)
            auth += expires
            headers['api-expires'] = expires
            if method == 'POST' or method == 'PUT' or method == 'DELETE':
                if params:
                    body = self.json(params)
                    auth += body
            headers['api-signature'] = self.hmac(self.encode(auth), self.encode(self.secret))
        return {'url': url, 'method': method, 'body': body, 'headers': headers}
