# -*- 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.async_support.base.exchange import Exchange
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidAddress
from ccxt.base.errors import InvalidOrder
from ccxt.base.precise import Precise


class xena(Exchange):

    def describe(self):
        return self.deep_extend(super(xena, self).describe(), {
            'id': 'xena',
            'name': 'Xena Exchange',
            'countries': ['VC', 'UK'],
            'rateLimit': 100,
            'certified': True,
            'has': {
                'CORS': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'createDepositAddress': True,
                'createOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchLedger': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrderBook': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/87489843-bb469280-c64c-11ea-91aa-69c6326506af.jpg',
                'test': {
                    'public': 'https://trading.demo.xena.io/api',
                    'private': 'https://api.demo.xena.io',
                },
                'api': {
                    'public': 'https://trading.xena.exchange/api',
                    'private': 'https://api.xena.exchange',
                },
                'www': 'https://xena.exchange',
                'doc': 'https://support.xena.exchange/support/solutions/44000808700',
                'fees': 'https://trading.xena.exchange/en/platform-specification/fee-schedule',
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '4h': '4h',
                '12h': '12h',
                '1d': '1d',
                '1w': '1w',
            },
            'api': {
                'public': {
                    'get': [
                        'common/currencies',
                        'common/instruments',
                        'common/features',
                        'common/commissions',
                        'common/news',
                        'market-data/candles/{marketId}/{timeframe}',
                        'market-data/market-watch',
                        'market-data/dom/{symbol}',
                        'market-data/candles/{symbol}/{timeframe}',
                        'market-data/trades/{symbol}',
                        'market-data/server-time',
                        'market-data/v2/candles/{symbol}/{timeframe}',
                        'market-data/v2/trades/{symbol}',
                        'market-data/v2/dom/{symbol}/',
                        'market-data/v2/server-time',
                    ],
                },
                'private': {
                    'get': [
                        'trading/accounts/{accountId}/order',
                        'trading/accounts/{accountId}/active-orders',
                        'trading/accounts/{accountId}/last-order-statuses',
                        'trading/accounts/{accountId}/positions',
                        'trading/accounts/{accountId}/positions-history',
                        'trading/accounts/{accountId}/margin-requirements',
                        'trading/accounts',
                        'trading/accounts/{accountId}/balance',
                        'trading/accounts/{accountId}/trade-history',
                        # 'trading/accounts/{accountId}/trade-history?symbol=BTC/USDT&client_order_id=EMBB8Veke&trade_id=220143254',
                        'transfers/accounts',
                        'transfers/accounts/{accountId}',
                        'transfers/accounts/{accountId}/deposit-address/{currency}',
                        'transfers/accounts/{accountId}/deposits',
                        'transfers/accounts/{accountId}/trusted-addresses',
                        'transfers/accounts/{accountId}/withdrawals',
                        'transfers/accounts/{accountId}/balance-history',
                        # 'transfers/accounts/{accountId}/balance-history?currency={currency}&from={time}&to={time}&kind={kind}&kind={kind}',
                        # 'transfers/accounts/{accountId}/balance-history?page={page}&limit={limit}',
                        # 'transfers/accounts/{accountId}/balance-history?txid=3e1db982c4eed2d6355e276c5bae01a52a27c9cef61574b0e8c67ee05fc26ccf',
                    ],
                    'post': [
                        'trading/order/new',
                        'trading/order/heartbeat',
                        'trading/order/cancel',
                        'trading/order/mass-cancel',
                        'trading/order/replace',
                        'trading/position/maintenance',
                        'transfers/accounts/{accountId}/withdrawals',
                        'transfers/accounts/{accountId}/deposit-address/{currency}',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.0005,
                    'taker': 0.001,
                    'tierBased': True,
                    'percentage': True,
                },
                'funding': {
                    'tierBased': False,
                    'percentage': False,
                    'withdraw': {},
                    'deposit': {},
                },
            },
            'exceptions': {
                'exact': {
                    'Validation failed': BadRequest,
                    'Unknown derivative symbol': BadSymbol,  # {"error":"Unknown derivative symbol"}
                    'Unknown account': BadRequest,  # {"error":"Unknown account"}
                    'Wrong TransactTime': BadRequest,  # {"error":"Wrong TransactTime"}
                    'ClOrdId is empty': BadRequest,  # {"error":"ClOrdId is empty"}
                },
                'broad': {
                    'Invalid aggregation ratio or depth': BadRequest,
                    'address': InvalidAddress,
                    'Money not enough': InsufficientFunds,
                    'parse error': BadRequest,
                    'Not enough': InsufficientFunds,  # {"error":"Not enough free margin"}
                },
            },
            'options': {
                'defaultType': 'margin',  # 'margin',
                'accountId': None,  # '1012838157',
            },
        })

    async def fetch_time(self, params={}):
        response = await self.publicGetMarketDataV2ServerTime(params)
        #
        #     {
        #         "msgType":"0",
        #         "transactTime":1594774454112817637
        #     }
        #
        transactTime = self.safe_integer(response, 'transactTime')
        return int(transactTime / 1000000)

    async def fetch_markets(self, params={}):
        response = await self.publicGetCommonInstruments(params)
        #
        #     [
        #         {
        #             "id":"ETHUSD_3M_250920",
        #             "type":"Margin",
        #             "marginType":"XenaFuture",
        #             "symbol":"ETHUSD_3M_250920",
        #             "baseCurrency":"ETH",
        #             "quoteCurrency":"USD",
        #             "settlCurrency":"BTC",
        #             "tickSize":2,
        #             "minOrderQuantity":"1",
        #             "orderQtyStep":"1",
        #             "limitOrderMaxDistance":"10",
        #             "priceInputMask":"0000.00",
        #             "enabled":true,
        #             "liquidationMaxDistance":"0.01",
        #             "contractValue":"1",
        #             "contractCurrency":"BTC",
        #             "lotSize":"1",
        #             "tickValue":"0.00000001",  # linear contracts only
        #             "maxOrderQty":"175000",
        #             "maxPosVolume":"1750000",
        #             "mark":".ETHUSD_3M_250920",
        #             "underlying":".ETHUSD_TWAP",
        #             "openInterest":".ETHUSD_3M_250920_OpenInterest",
        #             "floatingPL":"BidAsk",  # perpetual contracts only
        #             "addUvmToFreeMargin":"ProfitAndLoss",
        #             "margin":{
        #                 "netting":"PositionsAndOrders",
        #                 "rates":[
        #                     {"maxVolume":"175000","initialRate":"0.05","maintenanceRate":"0.0125"},
        #                     {"maxVolume":"350000","initialRate":"0.1","maintenanceRate":"0.025"},
        #                     {"maxVolume":"500000","initialRate":"0.2","maintenanceRate":"0.05"},
        #                     {"maxVolume":"750000","initialRate":"0.3","maintenanceRate":"0.075"},
        #                     {"maxVolume":"1050000","initialRate":"0.4","maintenanceRate":"0.1"},
        #                     {"maxVolume":"1400000","initialRate":"0.5","maintenanceRate":"0.125"},
        #                     {"maxVolume":"1750000","initialRate":"1","maintenanceRate":"0.25"}
        #                 ],
        #                 "rateMultipliers":{
        #                     "LimitBuy":"1",
        #                     "LimitSell":"1",
        #                     "Long":"1",
        #                     "MarketBuy":"1",
        #                     "MarketSell":"1",
        #                     "Short":"1",
        #                     "StopBuy":"0",
        #                     "StopSell":"0"
        #                 }
        #             },
        #             "clearing":{"enabled":true,"index":".ETHUSD_3M_250920"},
        #             "premium":{"enabled":true,"index":".XBTUSD_Premium_IR_Corrected"},  # perpetual contracts only
        #             "riskAdjustment":{"enabled":true,"index":".RiskAdjustment_IR"},
        #             "expiration":{"enabled":true,"index":".ETHUSD_TWAP"},  # futures only
        #             "pricePrecision":3,
        #             "priceRange":{
        #                 "enabled":true,
        #                 "distance":"0.03",
        #                 "movingBoundary":"0",
        #                 "lowIndex":".ETHUSD_3M_250920_LOWRANGE",
        #                 "highIndex":".ETHUSD_3M_250920_HIGHRANGE"
        #             },
        #             "priceLimits":{
        #                 "enabled":true,
        #                 "distance":"0.5",
        #                 "movingBoundary":"0",
        #                 "lowIndex":".ETHUSD_3M_250920_LOWLIMIT",
        #                 "highIndex":".ETHUSD_3M_250920_HIGHLIMIT"
        #             },
        #             "inverse":true,  # inverse contracts only
        #             "serie":"ETHUSD",  # futures only
        #             "tradingStartDate":"2020-03-27 07:00:00",
        #             "expiryDate":"2020-09-25 08:00:00"  # futures only
        #         },
        #         {
        #             "type":"Index",
        #             "symbol":".ETHUSD_Premium_IR_Corrected",
        #             "tickSize":6,
        #             "enabled":true,
        #             "basis":365
        #         },
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            market = response[i]
            type = self.safe_string_lower(market, 'type')
            id = self.safe_string(market, 'symbol')
            numericId = self.safe_string(market, 'id')
            marginType = self.safe_string(market, 'marginType')
            baseId = self.safe_string(market, 'baseCurrency')
            quoteId = self.safe_string(market, 'quoteCurrency')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = id
            if type == 'margin':
                if marginType == 'XenaFuture':
                    type = 'future'
                elif marginType == 'XenaListedPerpetual':
                    type = 'swap'
                    symbol = base + '/' + quote
            future = (type == 'future')
            swap = (type == 'swap')
            pricePrecision = self.safe_integer_2(market, 'tickSize', 'pricePrecision')
            precision = {
                'price': pricePrecision,
                'amount': 0,
            }
            maxCost = self.safe_number(market, 'maxOrderQty')
            minCost = self.safe_number(market, 'minOrderQuantity')
            limits = {
                'amount': {
                    'min': None,
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': minCost,
                    'max': maxCost,
                },
            }
            active = self.safe_value(market, 'enabled', False)
            inverse = self.safe_value(market, 'inverse', False)
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'numericId': numericId,
                'active': active,
                'type': type,
                'spot': False,
                'future': future,
                'swap': swap,
                'inverse': inverse,
                'precision': precision,
                'limits': limits,
                'info': market,
            })
        return result

    async def fetch_currencies(self, params={}):
        response = await self.publicGetCommonCurrencies(params)
        #
        #     {
        #         "BAB": {
        #             "name":"BAB",
        #             "title":"Bitcoin ABC",
        #             "blockchain":{
        #                 "name":"BAB",
        #                 "title":"Bitcoin ABC",
        #                 "deposit":{"confirmations":6},
        #                 "withdraw":{"confirmations":1},
        #                 "addressReuseAllowed":false,
        #                 "view":{
        #                     "uriTemplate":"bitcoinabc:%s?message=Xena Exchange",
        #                     "recommendedFee":"0.00001",
        #                     "transactionUrl":"https://blockchair.com/bitcoin-cash/transaction/${txId}",
        #                     "walletUrl":"https://blockchair.com/bitcoin-cash/address/${walletId}"
        #                 }
        #             },
        #             "precision":5,
        #             "withdraw":{"minAmount":"0.01","commission":"0.001"},
        #             "view":{
        #                 "color":"#DC7C08",
        #                 "site":"https://www.bitcoinabc.org"
        #             },
        #             "enabled":true
        #         },
        #     }
        ids = list(response.keys())
        result = {}
        for i in range(0, len(ids)):
            id = ids[i]
            currency = response[id]
            code = self.safe_currency_code(id)
            name = self.safe_string(currency, 'title')
            precision = self.safe_integer(currency, 'precision')
            enabled = self.safe_value(currency, 'enabled')
            active = (enabled is True)
            withdraw = self.safe_value(currency, 'withdraw', {})
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': name,
                'active': active,
                'fee': self.safe_number(withdraw, 'commission'),
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_number(withdraw, 'minAmount'),
                        'max': None,
                    },
                },
            }
        return result

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker, fetchTickers
        #
        #     {
        #         "symbol":".XBTUSD_3M_250920_MID",
        #         "firstPx":"9337.49",
        #         "lastPx":"9355.81",
        #         "highPx":"9579.42",
        #         "lowPx":"9157.63",
        #         "buyVolume":"0",
        #         "sellVolume":"0",
        #         "bid":"0",
        #         "ask":"0"
        #     }
        #
        timestamp = self.milliseconds()
        marketId = self.safe_string(ticker, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        last = self.safe_number(ticker, 'lastPx')
        open = self.safe_number(ticker, 'firstPx')
        percentage = None
        change = None
        average = None
        if (last is not None) and (open is not None):
            change = last - open
            average = self.sum(last, open) / 2
            if open > 0:
                percentage = change / open * 100
        buyVolume = self.safe_number(ticker, 'buyVolume')
        sellVolume = self.safe_number(ticker, 'sellVolume')
        baseVolume = self.sum(buyVolume, sellVolume)
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_number(ticker, 'highPx'),
            'low': self.safe_number(ticker, 'lowPx'),
            'bid': self.safe_number(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_number(ticker, 'ask'),
            'askVolume': None,
            'vwap': None,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': None,
            'info': ticker,
        }

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        tickers = await self.fetch_tickers(None, params)
        if symbol in tickers:
            return tickers[symbol]
        raise BadSymbol(self.id + ' fetchTicker could not find a ticker with symbol ' + symbol)

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        tickers = await self.publicGetMarketDataMarketWatch(params)
        #
        #     [
        #         {
        #             "symbol":".XBTUSD_3M_250920_MID",
        #             "firstPx":"9337.49",
        #             "lastPx":"9355.81",
        #             "highPx":"9579.42",
        #             "lowPx":"9157.63",
        #             "buyVolume":"0",
        #             "sellVolume":"0",
        #             "bid":"0",
        #             "ask":"0"
        #         }
        #     ]
        #
        result = {}
        for i in range(0, len(tickers)):
            ticker = self.parse_ticker(tickers[i])
            symbol = ticker['symbol']
            result[symbol] = ticker
        return self.filter_by_array(result, 'symbol', symbols)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        request = {
            'symbol': self.market_id(symbol),
        }
        if limit is not None:
            request['depth'] = limit
        response = await self.publicGetMarketDataV2DomSymbol(self.extend(request, params))
        #
        #     {
        #         "msgType":"W",
        #         "mdStreamId":"DOM:XBTUSD:aggregated",
        #         "lastUpdateTime":1594772683037691997,
        #         "mdBookType":"2",
        #         "symbol":"XBTUSD",
        #         "lowRangePx":"9132.24",
        #         "highRangePx":"9410.36",
        #         "lowLimitPx":"9132.24",
        #         "highLimitPx":"9410.36",
        #         "clearingPx":"9253.4",
        #         "bestBid":"9269.8",
        #         "bestAsk":"9275.9",
        #         "mdEntry":[
        #             {"mdEntryType":"1","mdEntryPx":"9275.9","mdEntrySize":"3000","numberOfOrders":1},
        #             {"mdEntryType":"1","mdEntryPx":"9277.7","mdEntrySize":"50000","numberOfOrders":1},
        #             {"mdEntryType":"1","mdEntryPx":"9277.8","mdEntrySize":"2000","numberOfOrders":1},
        #             {"mdEntryType":"0","mdEntryPx":"9269.8","mdEntrySize":"2000","numberOfOrders":1},
        #             {"mdEntryType":"0","mdEntryPx":"9267.9","mdEntrySize":"3000","numberOfOrders":1},
        #             {"mdEntryType":"0","mdEntryPx":"9267.8","mdEntrySize":"50000","numberOfOrders":1},
        #         ]
        #     }
        #
        mdEntry = self.safe_value(response, 'mdEntry', [])
        mdEntriesByType = self.group_by(mdEntry, 'mdEntryType')
        lastUpdateTime = self.safe_integer(response, 'lastUpdateTime')
        timestamp = int(lastUpdateTime / 1000000)
        return self.parse_order_book(mdEntriesByType, symbol, timestamp, '0', '1', 'mdEntryPx', 'mdEntrySize')

    async def fetch_accounts(self, params={}):
        response = await self.privateGetTradingAccounts(params)
        #
        #     {
        #         "accounts": [
        #             {"id":8273231, "kind": "Spot"},
        #             {"id":10012833469, "kind": "Margin", "currency": "BTC"}
        #         ]
        #     }
        #
        accounts = self.safe_value(response, 'accounts')
        result = []
        for i in range(0, len(accounts)):
            account = accounts[i]
            accountId = self.safe_string(account, 'id')
            currencyId = self.safe_string(account, 'currency')
            code = self.safe_currency_code(currencyId)
            type = self.safe_string_lower(account, 'kind')
            result.append({
                'id': accountId,
                'type': type,
                'currency': code,
                'info': account,
            })
        return result

    async def find_account_by_type(self, type):
        await self.load_markets()
        await self.load_accounts()
        accountsByType = self.group_by(self.accounts, 'type')
        accounts = self.safe_value(accountsByType, type)
        if accounts is None:
            raise ExchangeError(self.id + " findAccountByType() could not find an accountId with type '" + type + "', specify the 'accountId' parameter instead")  # eslint-disable-line quotes
        numAccounts = len(accounts)
        if numAccounts > 1:
            raise ExchangeError(self.id + " findAccountByType() found more than one accountId with type '" + type + "', specify the 'accountId' parameter instead")  # eslint-disable-line quotes
        return accounts[0]

    async def get_account_id(self, params):
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_string(self.options, 'accountId')
        accountId = self.safe_string(params, 'accountId', defaultAccountId)
        if accountId is not None:
            return accountId
        defaultType = self.safe_string(self.options, 'defaultType', 'margin')
        type = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        if type is None:
            raise ArgumentsRequired(self.id + " requires an 'accountId' parameter or a 'type' parameter('spot' or 'margin')")
        account = await self.find_account_by_type(type)
        return account['id']

    async def fetch_balance(self, params={}):
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        request = {
            'accountId': accountId,
        }
        response = await self.privateGetTradingAccountsAccountIdBalance(self.extend(request, params))
        #
        #     {
        #         "msgType":"XAR",
        #         "balances":[
        #             {
        #                 "currency":"BTC",
        #                 "lastUpdateTime":1619384111905916598,
        #                 "available":"0.00549964",
        #                 "onHold":"0",
        #                 "settled":"0.00549964",
        #                 "equity":"0.00549964"
        #             }
        #         ]
        #     }
        #
        result = {'info': response}
        timestamp = None
        balances = self.safe_value(response, 'balances', [])
        for i in range(0, len(balances)):
            balance = balances[i]
            lastUpdateTime = self.safe_string(balance, 'lastUpdateTime')
            lastUpdated = lastUpdateTime[0:13]
            currentTimestamp = int(lastUpdated)
            timestamp = currentTimestamp if (timestamp is None) else max(timestamp, currentTimestamp)
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(balance, 'available')
            account['used'] = self.safe_string(balance, 'onHold')
            result[code] = account
        result['timestamp'] = timestamp
        result['datetime'] = self.iso8601(timestamp)
        return self.parse_balance(result, False)

    def parse_trade(self, trade, market=None):
        #
        #     {
        #         "mdUpdateAction":"0",
        #         "mdEntryType":"2",
        #         "mdEntryPx":"9225.16",
        #         "mdEntrySize":"10000",
        #         "transactTime":1594728504524977655,
        #         "tradeId":"6ac51bb7-7505-4f35-85ef-61eb738cb4d9",
        #         "aggressorSide":"1"
        #     }
        #
        # fetchMyTrades
        #
        #     {
        #         "msgType":"8",
        #         "account":1012838158,
        #         "clOrdId":"xXWKLQVl3",
        #         "orderId":"89eee8bd-98ae-4d06-97dc-ee2d12997fe7",
        #         "symbol":"ETHUSD",
        #         "transactTime":1595143349089739000,
        #         "execId":"c4bd0ee2330930924e0f6fdde4630e56751692a4",
        #         "tradeId":"30a394b2-6d53-4bc4-b276-d8e19f470ba1",
        #         "side":"2",
        #         "lastQty":"1",
        #         "lastPx":"234.58",
        #         "avgPx":"234.58",
        #         "calculatedCcyLastQty":"0",
        #         "netMoney":"0",
        #         "lastLiquidityInd":"2",
        #         "commission":"0.00000011",
        #         "commRate":"0.00045",
        #         "commCurrency":"BTC",
        #         "positionId":132162662,
        #         "positionEffect":"C"
        #     }
        #
        id = self.safe_string(trade, 'tradeId')
        timestamp = self.safe_integer(trade, 'transactTime')
        if timestamp is not None:
            timestamp = int(timestamp / 1000000)
        side = self.safe_string_lower_2(trade, 'side', 'aggressorSide')
        if side == '1':
            side = 'buy'
        elif side == '2':
            side = 'sell'
        orderId = self.safe_string(trade, 'orderId')
        marketId = self.safe_string(trade, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        priceString = self.safe_string_2(trade, 'lastPx', 'mdEntryPx')
        amountString = self.safe_string_2(trade, 'lastQty', 'mdEntrySize')
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        cost = self.parse_number(Precise.string_mul(priceString, amountString))
        fee = None
        feeCost = self.safe_number(trade, 'commission')
        if feeCost is not None:
            feeCurrencyId = self.safe_string(trade, 'commCurrency')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            feeRate = self.safe_number(trade, 'commRate')
            fee = {
                'cost': feeCost,
                'rate': feeRate,
                'currency': feeCurrencyCode,
            }
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': None,
            'order': orderId,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        request = {
            'accountId': accountId,
            # 'page': 1,
            # 'limit': integer,
            # 'from': time,
            # 'to': time,
            # 'symbol': currency['id'],
            # 'trade_id': id,
            # 'client_order_id': id,
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if since is not None:
            request['from'] = since * 1000000
        if limit is not None:
            request['limit'] = limit
        response = await self.privateGetTradingAccountsAccountIdTradeHistory(self.extend(request, params))
        #
        #     [
        #         {
        #             "msgType":"8",
        #             "account":1012838158,
        #             "clOrdId":"xXWKLQVl3",
        #             "orderId":"89eee8bd-98ae-4d06-97dc-ee2d12997fe7",
        #             "symbol":"ETHUSD",
        #             "transactTime":1595143349089739000,
        #             "execId":"c4bd0ee2330930924e0f6fdde4630e56751692a4",
        #             "tradeId":"30a394b2-6d53-4bc4-b276-d8e19f470ba1",
        #             "side":"2",
        #             "lastQty":"1",
        #             "lastPx":"234.58",
        #             "avgPx":"234.58",
        #             "calculatedCcyLastQty":"0",
        #             "netMoney":"0",
        #             "lastLiquidityInd":"2",
        #             "commission":"0.00000011",
        #             "commRate":"0.00045",
        #             "commCurrency":"BTC",
        #             "positionId":132162662,
        #             "positionEffect":"C"
        #         },
        #         {
        #             "msgType":"8",
        #             "account":1012838158,
        #             "clOrdId":"3ce8c305-9936-4e97-9206-71ae3ff40305",
        #             "orderId":"a93c686d-990e-44d9-9cbe-61107744b990",
        #             "symbol":"ETHUSD",
        #             "transactTime":1595143315369226000,
        #             "execId":"1c745881722ad966a4ce71600cd058d59da0d1c3",
        #             "tradeId":"77f75bd8-27c4-4b1a-a5e8-0d59239ce216",
        #             "side":"1",
        #             "lastQty":"1",
        #             "lastPx":"234.72",
        #             "avgPx":"234.72",
        #             "calculatedCcyLastQty":"0",
        #             "netMoney":"0",
        #             "lastLiquidityInd":"2",
        #             "commission":"0.00000011",
        #             "commRate":"0.00045",
        #             "commCurrency":"BTC",
        #             "positionId":132162662,
        #             "positionEffect":"O"
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "transactTime":1594784700000000000,
        #         "firstPx":"9246.3",
        #         "lastPx":"9232.8",
        #         "highPx":"9246.3",
        #         "lowPx":"9232.8",
        #         "buyVolume":"0",
        #         "sellVolume":"0"
        #     }
        #
        transactTime = self.safe_integer(ohlcv, 'transactTime')
        timestamp = int(transactTime / 1000000)
        buyVolume = self.safe_number(ohlcv, 'buyVolume')
        sellVolume = self.safe_number(ohlcv, 'sellVolume')
        volume = self.sum(buyVolume, sellVolume)
        return [
            timestamp,
            self.safe_number(ohlcv, 'firstPx'),
            self.safe_number(ohlcv, 'highPx'),
            self.safe_number(ohlcv, 'lowPx'),
            self.safe_number(ohlcv, 'lastPx'),
            volume,
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'timeframe': self.timeframes[timeframe],
        }
        durationInSeconds = self.parse_timeframe(timeframe)
        duration = durationInSeconds * 1000
        if since is not None:
            request['from'] = since * 1000000
            if limit is not None:
                request['to'] = self.sum(since, limit * duration) * 1000000
        else:
            now = self.milliseconds()
            # max limit is 1000
            if limit is not None:
                request['from'] = (now - limit * duration) * 1000000
        response = await self.publicGetMarketDataV2CandlesSymbolTimeframe(self.extend(request, params))
        #
        #     {
        #         "mdEntry":[
        #             {"transactTime":1594784700000000000,"firstPx":"9246.3","lastPx":"9232.8","highPx":"9246.3","lowPx":"9232.8","buyVolume":"0","sellVolume":"0"},
        #             {"transactTime":1594785600000000000,"firstPx":"9231.8","lastPx":"9227.3","highPx":"9232.8","lowPx":"9227.3","buyVolume":"0","sellVolume":"0"},
        #             {"transactTime":1594786500000000000,"firstPx":"9226.3","lastPx":"9230.3","highPx":"9230.3","lowPx":"9220.6","buyVolume":"0","sellVolume":"0"}
        #         ]
        #     }
        #
        mdEntry = self.safe_value(response, 'mdEntry', [])
        return self.parse_ohlcvs(mdEntry, market, timeframe, since, limit)

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            # 'from': self.iso8601(since),
            # 'to': self.iso8601(self.milliseconds()),
            # 'page': 1,
            # 'limit': limit,
        }
        if since is not None:
            request['from'] = self.iso8601(since)
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetMarketDataV2TradesSymbol(self.extend(request, params))
        #
        #     {
        #         "msgType":"W",
        #         "lastUpdateTime":1594737830902223803,
        #         "symbol":"XBTUSD",
        #         "mdEntry":[
        #             {
        #                 "mdUpdateAction":"0",
        #                 "mdEntryType":"2",
        #                 "mdEntryPx":"9225.16",
        #                 "mdEntrySize":"10000",
        #                 "transactTime":1594728504524977655,
        #                 "tradeId":"6ac51bb7-7505-4f35-85ef-61eb738cb4d9",
        #                 "aggressorSide":"1"
        #             },
        #         ]
        #     }
        #
        mdEntry = self.safe_value(response, 'mdEntry', [])
        return self.parse_trades(mdEntry, market, since, limit)

    def parse_order_status(self, status):
        statuses = {
            'A': 'open',  # PendingNew
            '0': 'open',  # New
            '1': 'open',  # PartiallyFilled
            '2': 'closed',  # Filled
            '6': 'canceled',  # PendingCancel
            '4': 'canceled',  # Cancelled
            'E': 'open',  # PendingReplace
            '8': 'rejected',  # Rejected
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "msgType":"8",
        #         "account":1012838720,
        #         "clOrdId":"XAq0pRQ1g",
        #         "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35",
        #         "symbol":"XBTUSD",
        #         "ordType":"2",
        #         "price":"9000",
        #         "transactTime":1593778763271127920,
        #         "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9",
        #         "execType":"I",
        #         "ordStatus":"0",
        #         "side":"1",
        #         "orderQty":"1",
        #         "leavesQty":"1",
        #         "cumQty":"0",
        #         "positionEffect":"O",
        #         "marginAmt":"0.00000556",
        #         "marginAmtType":"11"
        #     }
        #
        id = self.safe_string(order, 'orderId')
        clientOrderId = self.safe_string(order, 'clOrdId')
        transactTime = self.safe_integer(order, 'transactTime')
        timestamp = int(transactTime / 1000000)
        status = self.parse_order_status(self.safe_string(order, 'ordStatus'))
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        price = self.safe_number(order, 'price')
        amount = self.safe_number(order, 'orderQty')
        filled = self.safe_number(order, 'cumQty')
        remaining = self.safe_number(order, 'leavesQty')
        side = self.safe_string_lower(order, 'side')
        if side == '1':
            side = 'buy'
        elif side == '1':
            side = 'sell'
        type = self.safe_string_lower(order, 'ordType')
        if type == '1':
            type = 'market'
        elif type == '2':
            type = 'limit'
        elif type == '3':
            type = 'stop'
        elif type == '4':
            type = 'stop-limit'
        return self.safe_order({
            'id': id,
            'clientOrderId': clientOrderId,
            'info': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'amount': amount,
            'cost': None,
            'average': None,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': None,
            'trades': None,
        })

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        orderTypes = {
            'market': '1',
            'limit': '2',
            'stop': '3',
            'stop-limit': '4',
        }
        orderType = self.safe_string(orderTypes, type)
        if orderType is None:
            raise InvalidOrder(self.id + ' createOrder does not support order type ' + type + ', supported order types are market, limit, stop, stop-limit')
        orderSides = {
            'buy': '1',
            'sell': '2',
        }
        orderSide = self.safe_string(orderSides, side)
        if orderSide is None:
            raise InvalidOrder(self.id + ' createOrder does not support order side ' + side + ', supported order sides are buy, sell')
        market = self.market(symbol)
        request = {
            'account': int(accountId),
            'symbol': market['id'],
            'ordType': orderType,
            'side': orderSide,
            'orderQty': self.amount_to_precision(symbol, amount),
            'transactTime': self.milliseconds() * 1000000,
            # 'clOrdId': self.uuid(),  # required
            # 'price': self.price_to_precision(symbol, price),  # required for limit and stop-limit orders
            # 'stopPx': self.price_to_precision(symbol, stopPx),  # required for stop and stop-limit orders
            # 'timeInForce': '1',  # default '1' = GoodTillCancelled, '3' = ImmediateOrCancel, '4' = FillOrKill
            # 'execInst': '0',
            #     '0' = StayOnOfferSide, maker only, reject instead of aggressive execution
            #     '9' = PegToOfferSide, maker only, best available level instead of aggressive execution
            #     'o' = CancelOnConnectionLoss
            # 'positionID': 1013838923,  # required when positionEffect == 'C' with hedged accounting
            # 'positionEffect': 'O',  # 'C' = Close, 'O' = Open, send C along with the positionID if the order must close a position with hedged accounting mode
            # 'text': 'comment',  # optional
            # 'grpID': 'group-identifier',  # group identifier for cancel on disconnect orders
        }
        if (type == 'limit') or (type == 'stop-limit'):
            if price is None:
                raise InvalidOrder(self.id + ' createOrder() requires a price argument for order type ' + type)
            request['price'] = self.price_to_precision(symbol, price)
        if (type == 'stop') or (type == 'stop-limit'):
            stopPx = self.safe_number(params, 'stopPx')
            if stopPx is None:
                raise InvalidOrder(self.id + ' createOrder() requires a stopPx param for order type ' + type)
            request['stopPx'] = self.price_to_precision(symbol, stopPx)
            params = self.omit(params, 'stopPx')
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'clOrdId', self.uuid())
        if clientOrderId is not None:
            request['clOrdId'] = clientOrderId
            params = self.omit(params, ['clientOrderId', 'clOrdId'])
        response = await self.privatePostTradingOrderNew(self.extend(request, params))
        #
        #     {
        #         "msgType":"8",
        #         "account":1012838720,
        #         "clOrdId":"XAq0pRQ1g",
        #         "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35",
        #         "symbol":"XBTUSD",
        #         "ordType":"2",
        #         "price":"9000",
        #         "transactTime":1593778763271127920,
        #         "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9",
        #         "execType":"I",
        #         "ordStatus":"0",
        #         "side":"1",
        #         "orderQty":"1",
        #         "leavesQty":"1",
        #         "cumQty":"0",
        #         "positionEffect":"O",
        #         "marginAmt":"0.00000556",
        #         "marginAmtType":"11"
        #     }
        #
        return self.parse_order(response, market)

    async def edit_order(self, id, symbol, type, side, amount=None, price=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        market = self.market(symbol)
        request = {
            'account': int(accountId),
            'clOrdId': self.uuid(),
            'symbol': market['id'],
            'transactTime': self.milliseconds() * 1000000,
            # 'origClOrdId': self.uuid(),  # one of orderId or origClOrdId is required
            # 'orderId': id,
            # 'side': '1',  # 1 = buy, 2 = sell
            # 'execInst': '0',
            #     '0' = StayOnOfferSide, maker only, reject instead of aggressive execution
            #     '9' = PegToOfferSide, maker only, best available level instead of aggressive execution
            #     'o' = CancelOnConnectionLoss
            # 'orderQty': 38 M decimal
            # 'price': self.price_to_precision(symbol, price),  # required for limit and stop-limit orders
            # 'stopPx': self.price_to_precision(symbol, stopPx),  # required for stop and stop-limit orders
            # 'capPrice': self.price_to_precision(symbol, capPrice),  # the price beyond which the order will not move for trailing stop and attempt-zero-loss
            # 'pegPriceType': '8',  # '8' = TrailingStopPeg, identifies a trailing stop or an attempt-zero-loss order
            # 'pegOffsetType': '2',  # '2' = BasisPoints, the unit of the distance to the stop price for a trailing stop or an attempt-zero-loss order
            # 'pegOffsetValue': 123,  # distance to the trailing stop or attempt-zero-loss
        }
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'origClOrdId')
        if clientOrderId is not None:
            request['origClOrdId'] = clientOrderId
            params = self.omit(params, ['clientOrderId', 'origClOrdId'])
        else:
            request['orderId'] = id
        if amount is not None:
            request['orderQty'] = self.amount_to_precision(symbol, amount)
        if price is not None:
            request['price'] = self.price_to_precision(symbol, price)
        stopPx = self.safe_number(params, 'stopPx')
        if stopPx is not None:
            request['stopPx'] = self.price_to_precision(symbol, stopPx)
            params = self.omit(params, 'stopPx')
        capPrice = self.safe_number(params, 'capPrice')
        if capPrice is not None:
            request['capPrice'] = self.price_to_precision(symbol, capPrice)
            params = self.omit(params, 'capPrice')
        response = await self.privatePostTradingOrderReplace(self.extend(request, params))
        return self.parse_order(response, market)

    async def cancel_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'origClOrdId')
        params = self.omit(params, ['clientOrderId', 'origClOrdId'])
        market = self.market(symbol)
        request = {
            'account': int(accountId),
            'symbol': market['id'],
            'clOrdId': self.uuid(),
            'transactTime': self.milliseconds() * 1000000,
        }
        if clientOrderId is not None:
            request['origClOrdId'] = clientOrderId
        else:
            request['orderId'] = id
        response = await self.privatePostTradingOrderCancel(self.extend(request, params))
        #
        #     {
        #         "msgType":"8",
        #         "account":1012838158,
        #         "clOrdId":"0fa3fb55-9dc0-4cfc-a1db-6aa8b7dd2d98",
        #         "origClOrdId":"3b2878bb-24d8-4922-9d2a-5b8009416677",
        #         "orderId":"665b418e-9d09-4461-b733-d317f6bff43f",
        #         "symbol":"ETHUSD",
        #         "ordType":"2",
        #         "price":"640",
        #         "transactTime":1595060080941618739,
        #         "execId":"c541c0ca437c0e6501c3a50a9d4dc8f575f49972",
        #         "execType":"6",
        #         "ordStatus":"6",
        #         "side":"2",
        #         "orderQty":"1",
        #         "leavesQty":"0",
        #         "cumQty":"0",
        #         "positionEffect":"O",
        #         "marginAmt":"0.000032",
        #         "marginAmtType":"11"
        #     }
        #
        return self.parse_order(response, market)

    async def cancel_all_orders(self, symbol=None, params={}):
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        request = {
            'account': int(accountId),
            'clOrdId': self.uuid(),
            # 'side': '1',  # 1 = buy, 2 = sell, optional filter, cancel only orders with the given side
            # 'positionEffect': 'C',  # C = Close, O = Open, optional filter, cancel only orders with the given positionEffect, applicable only for accounts with hedged accounting
        }
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
            request['massCancelRequestType'] = '1'  # CancelOrdersForASecurity
        else:
            request['massCancelRequestType'] = '7'  # CancelAllOrders
        response = await self.privatePostTradingOrderMassCancel(self.extend(request, params))
        #
        #     {
        #         "msgType":"r",
        #         "clOrdId":"b3e95759-e43e-4b3a-b664-a4d213e281a7",
        #         "massActionReportID":"e915b6f4-a7ca-4c5c-b8d6-e39862530248",
        #         "massCancelResponse":"1",
        #         "symbol":"ETHUSD",
        #         "transactTime":1595065630133756426,
        #         "totalAffectedOrders":2,
        #         "account":1012838158
        #     }
        #
        return response

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        request = {
            'accountId': accountId,
            # 'symbol': market['id'],
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        response = await self.privateGetTradingAccountsAccountIdActiveOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "msgType":"8",
        #             "account":1012838720,
        #             "clOrdId":"XAq0pRQ1g",
        #             "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35",
        #             "symbol":"XBTUSD",
        #             "ordType":"2",
        #             "price":"9000",
        #             "transactTime":1593778763271127920,
        #             "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9",
        #             "execType":"I",
        #             "ordStatus":"0",
        #             "side":"1",
        #             "orderQty":"1",
        #             "leavesQty":"1",
        #             "cumQty":"0",
        #             "positionEffect":"O",
        #             "marginAmt":"0.00000556",
        #             "marginAmtType":"11"
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        request = {
            'accountId': accountId,
            # 'from': self.iso8601(since) * 1000000,
            # 'to': self.iso8601(self.milliseconds()) * 1000000,  # max range is 7 days
            # 'symbol': market['id'],
            # 'limit': 100,
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if since is not None:
            request['from'] = self.iso8601(since) * 1000000
        if limit is not None:
            request['limit'] = limit
        response = await self.privateGetTradingAccountsAccountIdLastOrderStatuses(self.extend(request, params))
        #
        #     [
        #         {
        #             "msgType":"8",
        #             "account":1012838720,
        #             "clOrdId":"XAq0pRQ1g",
        #             "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35",
        #             "symbol":"XBTUSD",
        #             "ordType":"2",
        #             "price":"9000",
        #             "transactTime":1593778763271127920,
        #             "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9",
        #             "execType":"I",
        #             "ordStatus":"0",
        #             "side":"1",
        #             "orderQty":"1",
        #             "leavesQty":"1",
        #             "cumQty":"0",
        #             "positionEffect":"O",
        #             "marginAmt":"0.00000556",
        #             "marginAmtType":"11"
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    async def create_deposit_address(self, code, params={}):
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        currency = self.currency(code)
        request = {
            'accountId': accountId,
            'currency': currency['id'],
        }
        response = await self.privatePostTransfersAccountsAccountIdDepositAddressCurrency(self.extend(request, params))
        #
        #     {
        #         "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        #         "uri": "bitcoin:mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9?message=Xena Exchange",
        #         "allowsRenewal": True
        #     }
        #
        address = self.safe_value(response, 'address')
        tag = None
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': response,
        }

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        currency = self.currency(code)
        request = {
            'accountId': accountId,
            'currency': currency['id'],
        }
        response = await self.privateGetTransfersAccountsAccountIdDepositAddressCurrency(self.extend(request, params))
        #
        #     {
        #         "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        #         "uri": "bitcoin:mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9?message=Xena Exchange",
        #         "allowsRenewal": True
        #     }
        #
        address = self.safe_value(response, 'address')
        tag = None
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': response,
        }

    async def fetch_transactions_by_type(self, type, code=None, since=None, limit=None, params={}):
        if code is None:
            raise ArgumentsRequired(self.id + ' fetchTransactions() requires a currency `code` argument')
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
            'accountId': accountId,
        }
        if since is not None:
            request['since'] = int(since / 1000)
        method = 'privateGetTransfersAccountsAccountId' + self.capitalize(type)
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "withdrawals": [
        #             {
        #                 "withdrawalRequestId": 47383243,
        #                 "externalId": "...",    # external ID submitted by the client when creating the request
        #                 "status": 1,
        #                 "statusMessage": "Pending confirmation",
        #                 "amount": "10.2",
        #                 "currency": "BTC",
        #                 "lastUpdated": <UNIX nanoseconds>,
        #                 "blockchain": "Bitcoin",
        #                 "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        #                 "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98"
        #             }
        #         ]
        #     }
        #
        #     {
        #         "deposits": [
        #             {
        #                 "currency": "BTC",
        #                 "amount": "1.2",
        #                 "status": 1,
        #                 "statusMessage": "Processing",
        #                 "blockchain": "Bitcoin",
        #                 "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98",
        #                 "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        #                 "lastUpdated": <UNIX nanoseconds>
        #                 "confirmations": 2,
        #                 "requiredConfirmations": 6
        #             }
        #         ]
        #     }
        #
        #
        transactions = self.safe_value(response, type, [])
        return self.parse_transactions(transactions, currency, since, limit)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        return await self.fetch_transactions_by_type('withdrawals', code, since, limit, params)

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        return await self.fetch_transactions_by_type('deposits', code, since, limit, params)

    def parse_transaction(self, transaction, currency=None):
        #
        # withdraw()
        #
        #     {
        #         "withdrawalRequestId": 47383243,
        #         "status": 1,
        #         "statusMessage": "Pending confirmation"
        #     }
        #
        # fetchWithdrawals
        #
        #     {
        #         "withdrawalRequestId": 47383243,
        #         "externalId": "...",    # external ID submitted by the client when creating the request
        #         "status": 1,
        #         "statusMessage": "Pending confirmation",
        #         "amount": "10.2",
        #         "currency": "BTC",
        #         "lastUpdated": <UNIX nanoseconds>,
        #         "blockchain": "Bitcoin",
        #         "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        #         "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98"
        #     }
        #
        # fetchDeposits
        #
        #     {
        #         "currency": "BTC",
        #         "amount": "1.2",
        #         "status": 1,
        #         "statusMessage": "Processing",
        #         "blockchain": "Bitcoin",
        #         "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98",
        #         "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        #         "lastUpdated": <UNIX nanoseconds>
        #         "confirmations": 2,
        #         "requiredConfirmations": 6
        #     }
        #
        id = self.safe_string(transaction, 'withdrawalRequestId')
        type = 'deposit' if (id is None) else 'withdrawal'
        updated = self.safe_integer(transaction, 'lastUpdated')
        if updated is not None:
            updated = int(updated / 1000000)
        timestamp = None
        txid = self.safe_string(transaction, 'txId')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        address = self.safe_string(transaction, 'address')
        addressFrom = None
        addressTo = address
        amount = self.safe_number(transaction, 'amount')
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        fee = None
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'addressFrom': addressFrom,
            'addressTo': addressTo,
            'address': address,
            'tagFrom': None,
            'tagTo': None,
            'tag': None,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'fee': fee,
        }

    def parse_transaction_status(self, status):
        statuses = {
            '1': 'pending',  # new
            '2': 'ok',  # completed
            '3': 'failed',  # duplicate
            '4': 'failed',  # not enough money
            '5': 'pending',  # waiting for manual approval from XENA
            '100': 'pending',  # request is being processed
            '101': 'pending',  # request is being processed
            '102': 'pending',  # request is being processed
            '103': 'pending',  # request is being processed
        }
        return self.safe_string(statuses, status, status)

    async def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        currency = self.currency(code)
        uuid = self.uuid()
        uuid = uuid.split('-')
        uuid = ''.join(uuid)
        request = {
            'currency': currency['id'],
            'accountId': accountId,
            'amount': self.currency_to_precision(code, amount),
            'address': address,
            'id': uuid,  # mandatory external ID(string), used by the client to identify his request
        }
        response = await self.privatePostTransfersAccountsAccountIdWithdrawals(self.extend(request, params))
        #
        #     {
        #         "withdrawalRequestId": 47383243,
        #         "status": 1,
        #         "statusMessage": "Pending confirmation"
        #     }
        #
        return self.parse_transaction(response, currency)

    def parse_ledger_entry_type(self, type):
        types = {
            'deposit': 'transaction',
            'withdrawal': 'transaction',
            'internal deposit': 'transfer',
            'internal withdrawal': 'transfer',
            'rebate': 'rebate',
            'reward': 'reward',
        }
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item, currency=None):
        #
        #     {
        #         "accountId":8263118,
        #         "ts":1551974415000000000,
        #         "amount":"-1",
        #         "currency":"BTC",
        #         "kind":"internal withdrawal",
        #         "commission":"0",
        #         "id":96
        #     }
        #
        id = self.safe_string(item, 'id')
        direction = None
        account = self.safe_string(item, 'accountId')
        referenceId = None
        referenceAccount = None
        type = self.parse_ledger_entry_type(self.safe_string(item, 'kind'))
        code = self.safe_currency_code(self.safe_string(item, 'currency'), currency)
        amount = self.safe_number(item, 'amount')
        if amount < 0:
            direction = 'out'
            amount = abs(amount)
        else:
            direction = 'in'
        timestamp = self.safe_integer(item, 'ts')
        if timestamp is not None:
            timestamp = int(timestamp / 1000000)
        fee = {
            'cost': self.safe_number(item, 'commission'),
            'currency': code,
        }
        before = None
        after = self.safe_number(item, 'balance')
        status = 'ok'
        return {
            'info': item,
            'id': id,
            'direction': direction,
            'account': account,
            'referenceId': referenceId,
            'referenceAccount': referenceAccount,
            'type': type,
            'currency': code,
            'amount': amount,
            'before': before,
            'after': after,
            'status': status,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': fee,
        }

    async def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        await self.load_accounts()
        accountId = await self.get_account_id(params)
        request = {
            'accountId': accountId,
            # 'page': 1,
            # 'limit': 5000,  # max 5000
            # 'from': time,
            # 'to': time,
            # 'symbol': currency['id'],
            # 'trade_id': id,
            # 'client_order_id': id,
            # 'txid': txid,
            # 'kind': 'deposit',  # 'withdrawal, 'internal deposit', 'internal withdrawal', 'rebate', 'reward'
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['symbol'] = currency['id']
        if since is not None:
            request['from'] = since * 1000000
        if limit is not None:
            request['limit'] = limit  # max 5000
        response = await self.privateGetTransfersAccountsAccountIdBalanceHistory(self.extend(request, params))
        #
        #     [
        #         {
        #             "accountId":8263118,
        #             "ts":1551974415000000000,
        #             "amount":"-1",
        #             "currency":"BTC",
        #             "kind":"internal withdrawal",
        #             "commission":"0",
        #             "id":96
        #         },
        #         {
        #             "accountId":8263118,
        #             "ts":1551964677000000000,
        #             "amount":"-1",
        #             "currency":"BTC",
        #             "kind":"internal deposit",
        #             "commission":"0",
        #             "id":95
        #         }
        #     ]
        #
        return self.parse_ledger(response, currency, since, limit)

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

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api] + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if api == 'public':
            if query:
                url += '?' + self.urlencode(query)
        elif api == 'private':
            self.check_required_credentials()
            nonce = self.nonce()
            # php does not format it properly
            # therefore we use string concatenation here
            # nonce *= 1000000
            nonce = str(nonce)
            nonce = nonce + '000000'  # see the comment a few lines above
            payload = 'AUTH' + nonce
            secret = self.secret[14:78]
            ecdsa = self.ecdsa(payload, secret, 'p256', 'sha256')
            signature = ecdsa['r'] + ecdsa['s']
            headers = {
                'X-AUTH-API-KEY': self.apiKey,
                'X-AUTH-API-PAYLOAD': payload,
                'X-AUTH-API-SIGNATURE': signature,
                'X-AUTH-API-NONCE': nonce,
            }
            if method == 'GET':
                if query:
                    url += '?' + self.urlencode(query)
            elif method == 'POST':
                body = self.json(query)
                headers['Content-Type'] = 'application/json'
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return
        #
        #     {"error":"Validation failed","fields":["address"]}
        #     {"error":"Money not enough. You have only: 0 ETH","fields":["amount"]}
        #
        if code >= 400:
            feedback = self.id + ' ' + self.json(response)
            message = self.safe_string(response, 'error')
            exact = self.exceptions['exact']
            if message in exact:
                raise exact[message](feedback)
            broad = self.exceptions['broad']
            broadKey = self.find_broadly_matched_key(broad, body)
            if broadKey is not None:
                raise broad[broadKey](feedback)
            raise ExchangeError(feedback)  # unknown message
