# -*- 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 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',
            },
        })

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

    def fetch_markets(self, params={}):
        response = 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

    def fetch_currencies(self, params={}):
        response = 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,
        }

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        tickers = 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)

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        tickers = 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)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        request = {
            'symbol': self.market_id(symbol),
        }
        if limit is not None:
            request['depth'] = limit
        response = 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')

    def fetch_accounts(self, params={}):
        response = 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

    def find_account_by_type(self, type):
        self.load_markets()
        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]

    def get_account_id(self, params):
        self.load_markets()
        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 = self.find_account_by_type(type)
        return account['id']

    def fetch_balance(self, params={}):
        self.load_markets()
        self.load_accounts()
        accountId = self.get_account_id(params)
        request = {
            'accountId': accountId,
        }
        response = 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,
        }

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        self.load_accounts()
        accountId = 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 = 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,
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        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 = 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)

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        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 = 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,
        })

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        self.load_accounts()
        accountId = 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 = 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)

    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')
        self.load_markets()
        self.load_accounts()
        accountId = 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 = self.privatePostTradingOrderReplace(self.extend(request, params))
        return self.parse_order(response, market)

    def cancel_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        self.load_markets()
        self.load_accounts()
        accountId = 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 = 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)

    def cancel_all_orders(self, symbol=None, params={}):
        self.load_markets()
        self.load_accounts()
        accountId = 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 = 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

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        self.load_accounts()
        accountId = 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 = 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)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        self.load_accounts()
        accountId = 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 = 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)

    def create_deposit_address(self, code, params={}):
        self.load_markets()
        self.load_accounts()
        accountId = self.get_account_id(params)
        currency = self.currency(code)
        request = {
            'accountId': accountId,
            'currency': currency['id'],
        }
        response = 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,
        }

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        self.load_accounts()
        accountId = self.get_account_id(params)
        currency = self.currency(code)
        request = {
            'accountId': accountId,
            'currency': currency['id'],
        }
        response = 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,
        }

    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')
        self.load_markets()
        self.load_accounts()
        accountId = 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 = 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)

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

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        return 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)

    def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        self.load_markets()
        self.load_accounts()
        accountId = 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 = 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,
        }

    def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        self.load_accounts()
        accountId = 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 = 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
