# -*- 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
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import OnMaintenance
from ccxt.base.errors import InvalidNonce
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class exmo(Exchange):

    def describe(self):
        return self.deep_extend(super(exmo, self).describe(), {
            'id': 'exmo',
            'name': 'EXMO',
            'countries': ['LT'],  # Lithuania
            'rateLimit': 350,  # once every 350 ms ≈ 180 requests per minute ≈ 3 requests per second
            'version': 'v1.1',
            'has': {
                'CORS': None,
                'spot': True,
                'margin': True,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': True,
                'cancelOrder': True,
                'cancelOrders': False,
                'createDepositAddress': False,
                'createOrder': True,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'fetchAccounts': False,
                'fetchBalance': True,
                'fetchCanceledOrders': True,
                'fetchCurrencies': True,
                'fetchDeposit': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': 'emulated',
                'fetchOrderBook': True,
                'fetchOrderBooks': True,
                'fetchOrderTrades': True,
                'fetchPositionMode': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': True,
                'fetchTransactionFees': True,
                'fetchTransactions': True,
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawal': True,
                'fetchWithdrawals': True,
                'reduceMargin': True,
                'setMargin': False,
                'transfer': False,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1',
                '5m': '5',
                '15m': '15',
                '30m': '30',
                '45m': '45',
                '1h': '60',
                '2h': '120',
                '3h': '180',
                '4h': '240',
                '1d': 'D',
                '1w': 'W',
                '1M': 'M',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766491-1b0ea956-5eda-11e7-9225-40d67b481b8d.jpg',
                'api': {
                    'public': 'https://api.exmo.com',
                    'private': 'https://api.exmo.com',
                    'web': 'https://exmo.me',
                },
                'www': 'https://exmo.me',
                'referral': 'https://exmo.me/?ref=131685',
                'doc': [
                    'https://exmo.me/en/api_doc?ref=131685',
                ],
                'fees': 'https://exmo.com/en/docs/fees',
            },
            'api': {
                'web': {
                    'get': [
                        'ctrl/feesAndLimits',
                        'en/docs/fees',
                    ],
                },
                'public': {
                    'get': [
                        'currency',
                        'currency/list/extended',
                        'order_book',
                        'pair_settings',
                        'ticker',
                        'trades',
                        'candles_history',
                        'required_amount',
                        'payments/providers/crypto/list',
                    ],
                },
                'private': {
                    'post': [
                        'user_info',
                        'order_create',
                        'order_cancel',
                        'stop_market_order_create',
                        'stop_market_order_cancel',
                        'user_open_orders',
                        'user_trades',
                        'user_cancelled_orders',
                        'order_trades',
                        'deposit_address',
                        'withdraw_crypt',
                        'withdraw_get_txid',
                        'excode_create',
                        'excode_load',
                        'code_check',
                        'wallet_history',
                        'wallet_operations',
                        'margin/user/order/create',
                        'margin/user/order/update',
                        'margin/user/order/cancel',
                        'margin/user/position/close',
                        'margin/user/position/margin_add',
                        'margin/user/position/margin_remove',
                        'margin/currency/list',
                        'margin/pair/list',
                        'margin/settings',
                        'margin/funding/list',
                        'margin/user/info',
                        'margin/user/order/list',
                        'margin/user/order/history',
                        'margin/user/order/trades',
                        'margin/user/order/max_quantity',
                        'margin/user/position/list',
                        'margin/user/position/margin_remove_info',
                        'margin/user/position/margin_add_info',
                        'margin/user/wallet/list',
                        'margin/user/wallet/history',
                        'margin/user/trade/list',
                        'margin/trades',
                        'margin/liquidation/feed',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'feeSide': 'get',
                    'tierBased': True,
                    'percentage': True,
                    'maker': self.parse_number('0.004'),
                    'taker': self.parse_number('0.004'),
                },
                'transaction': {
                    'tierBased': False,
                    'percentage': False,  # fixed transaction fees for crypto, see fetchTransactionFees below
                },
            },
            'options': {
                'networks': {
                    'ETH': 'ERC20',
                    'TRX': 'TRC20',
                },
                'fetchTradingFees': {
                    'method': 'fetchPrivateTradingFees',  # or 'fetchPublicTradingFees'
                },
                'margin': {
                    'fillResponseFromRequest': True,
                },
            },
            'commonCurrencies': {
                'GMT': 'GMT Token',
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    '40005': AuthenticationError,  # Authorization error, incorrect signature
                    '40009': InvalidNonce,  #
                    '40015': ExchangeError,  # API function do not exist
                    '40016': OnMaintenance,  # {"result":false,"error":"Error 40016: Maintenance work in progress"}
                    '40017': AuthenticationError,  # Wrong API Key
                    '40032': PermissionDenied,  # {"result":false,"error":"Error 40032: Access is denied for self API key"}
                    '40033': PermissionDenied,  # {"result":false,"error":"Error 40033: Access is denied, self resources are temporarily blocked to user"}
                    '40034': RateLimitExceeded,  # {"result":false,"error":"Error 40034: Access is denied, rate limit is exceeded"}
                    '50052': InsufficientFunds,
                    '50054': InsufficientFunds,
                    '50304': OrderNotFound,  # "Order was not found '123456789'"(fetching order trades for an order that does not have trades yet)
                    '50173': OrderNotFound,  # "Order with id X was not found."(cancelling non-existent, closed and cancelled order)
                    '50277': InvalidOrder,
                    '50319': InvalidOrder,  # Price by order is less than permissible minimum for self pair
                    '50321': InvalidOrder,  # Price by order is more than permissible maximum for self pair
                    '50381': InvalidOrder,  # {"result":false,"error":"Error 50381: More than 2 decimal places are not permitted for pair BTC_USD"}
                },
                'broad': {
                    'range period is too long': BadRequest,
                    'invalid syntax': BadRequest,
                    'API rate limit exceeded': RateLimitExceeded,  # {"result":false,"error":"API rate limit exceeded for x.x.x.x. Retry after 60 sec.","history":[],"begin":1579392000,"end":1579478400}
                },
            },
        })

    def modify_margin_helper(self, symbol, amount, type, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'position_id': market['id'],
            'quantity': amount,
        }
        method = None
        if type == 'add':
            method = 'privatePostMarginUserPositionMarginAdd'
        elif type == 'reduce':
            method = 'privatePostMarginUserPositionMarginReduce'
        response = getattr(self, method)(self.extend(request, params))
        #
        #      {}
        #
        margin = self.parse_margin_modification(response, market)
        options = self.safe_value(self.options, 'margin', {})
        fillResponseFromRequest = self.safe_value(options, 'fillResponseFromRequest', True)
        if fillResponseFromRequest:
            margin['type'] = type
            margin['amount'] = amount
        return margin

    def parse_margin_modification(self, data, market=None):
        #
        #      {}
        #
        return {
            'info': data,
            'type': None,
            'amount': None,
            'code': self.safe_value(market, 'quote'),
            'symbol': self.safe_symbol(None, market),
            'total': None,
            'status': 'ok',
        }

    def reduce_margin(self, symbol, amount, params={}):
        """
        remove margin from a position
        :param str symbol: unified market symbol
        :param float amount: the amount of margin to remove
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a `margin structure <https://docs.ccxt.com/en/latest/manual.html#reduce-margin-structure>`
        """
        return self.modify_margin_helper(symbol, amount, 'reduce', params)

    def add_margin(self, symbol, amount, params={}):
        """
        add margin
        :param str symbol: unified market symbol
        :param float amount: amount of margin to add
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a `margin structure <https://docs.ccxt.com/en/latest/manual.html#add-margin-structure>`
        """
        return self.modify_margin_helper(symbol, amount, 'add', params)

    def fetch_trading_fees(self, params={}):
        """
        fetch the trading fees for multiple markets
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/en/latest/manual.html#fee-structure>` indexed by market symbols
        """
        method = self.safe_string(params, 'method')
        params = self.omit(params, 'method')
        if method is None:
            options = self.safe_value(self.options, 'fetchTradingFees', {})
            method = self.safe_string(options, 'method', 'fetchPrivateTradingFees')
        return getattr(self, method)(params)

    def fetch_private_trading_fees(self, params={}):
        self.load_markets()
        response = self.privatePostMarginPairList(params)
        #
        #     {
        #         pairs: [{
        #             name: 'EXM_USD',
        #             buy_price: '0.02728391',
        #             sell_price: '0.0276',
        #             last_trade_price: '0.0276',
        #             ticker_updated: '1646956050056696046',
        #             is_fair_price: True,
        #             max_price_precision: '8',
        #             min_order_quantity: '1',
        #             max_order_quantity: '50000',
        #             min_order_price: '0.00000001',
        #             max_order_price: '1000',
        #             max_position_quantity: '50000',
        #             trade_taker_fee: '0.05',
        #             trade_maker_fee: '0',
        #             liquidation_fee: '0.5',
        #             max_leverage: '3',
        #             default_leverage: '3',
        #             liquidation_level: '5',
        #             margin_call_level: '7.5',
        #             position: '1',
        #             updated: '1638976144797807397'
        #         }
        #         ...
        #         ]
        #     }
        #
        pairs = self.safe_value(response, 'pairs', [])
        result = {}
        for i in range(0, len(pairs)):
            pair = pairs[i]
            marketId = self.safe_string(pair, 'name')
            symbol = self.safe_symbol(marketId, None, '_')
            makerString = self.safe_string(pair, 'trade_maker_fee')
            takerString = self.safe_string(pair, 'trade_taker_fee')
            maker = self.parse_number(Precise.string_div(makerString, '100'))
            taker = self.parse_number(Precise.string_div(takerString, '100'))
            result[symbol] = {
                'info': pair,
                'symbol': symbol,
                'maker': maker,
                'taker': taker,
                'percentage': True,
                'tierBased': True,
            }
        return result

    def fetch_public_trading_fees(self, params={}):
        self.load_markets()
        response = self.publicGetPairSettings(params)
        #
        #     {
        #         BTC_USD: {
        #             min_quantity: '0.00002',
        #             max_quantity: '1000',
        #             min_price: '1',
        #             max_price: '150000',
        #             max_amount: '500000',
        #             min_amount: '1',
        #             price_precision: '2',
        #             commission_taker_percent: '0.3',
        #             commission_maker_percent: '0.3'
        #         },
        #     }
        #
        result = {}
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            market = self.market(symbol)
            fee = self.safe_value(response, market['id'], {})
            makerString = self.safe_string(fee, 'commission_maker_percent')
            takerString = self.safe_string(fee, 'commission_taker_percent')
            maker = self.parse_number(Precise.string_div(makerString, '100'))
            taker = self.parse_number(Precise.string_div(takerString, '100'))
            result[symbol] = {
                'info': fee,
                'symbol': symbol,
                'maker': maker,
                'taker': taker,
                'percentage': True,
                'tierBased': True,
            }
        return result

    def parse_fixed_float_value(self, input):
        if (input is None) or (input == '-'):
            return None
        if input == '':
            return 0
        isPercentage = (input.find('%') >= 0)
        parts = input.split(' ')
        value = parts[0].replace('%', '')
        result = float(value)
        if (result > 0) and isPercentage:
            raise ExchangeError(self.id + ' parseFixedFloatValue() detected an unsupported non-zero percentage-based fee ' + input)
        return result

    def fetch_transaction_fees(self, codes=None, params={}):
        """
        fetch transaction fees
        :param [str]|None codes: list of unified currency codes
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a list of `transaction fees structures <https://docs.ccxt.com/en/latest/manual.html#fees-structure>`
        """
        self.load_markets()
        currencyList = self.publicGetCurrencyListExtended(params)
        #
        #     [
        #         {"name":"VLX","description":"Velas"},
        #         {"name":"RUB","description":"Russian Ruble"},
        #         {"name":"BTC","description":"Bitcoin"},
        #         {"name":"USD","description":"US Dollar"}
        #     ]
        #
        cryptoList = self.publicGetPaymentsProvidersCryptoList(params)
        #
        #     {
        #         "BTC":[
        #             {"type":"deposit", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.001 BTC. We do not support BSC and BEP20 network, please consider self when sending funds", "commission_desc":"0%", "currency_confirmations":1},
        #             {"type":"withdraw", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"350", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.0005 BTC", "currency_confirmations":6}
        #         ],
        #         "ETH":[
        #             {"type":"withdraw", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"500", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.004 ETH", "currency_confirmations":4},
        #             {"type":"deposit", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.01 ETH. We do not support BSC and BEP20 network, please consider self when sending funds", "commission_desc":"0%", "currency_confirmations":1}
        #         ],
        #         "USDT":[
        #             {"type":"deposit", "name":"USDT(OMNI)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":false,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2},
        #             {"type":"withdraw", "name":"USDT(OMNI)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":false,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"5 USDT", "currency_confirmations":6},
        #             {"type":"deposit", "name":"USDT(ERC20)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2},
        #             {
        #                 "type":"withdraw",
        #                 "name":"USDT(ERC20)",
        #                 "currency_name":"USDT",
        #                 "min":"55",
        #                 "max":"200000",
        #                 "enabled":true,
        #                 "comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Recommendation: Due to the high load of ERC20 network, using TRC20 address for withdrawal is recommended.",
        #                 "commission_desc":"10 USDT",
        #                 "currency_confirmations":6
        #             },
        #             {"type":"deposit", "name":"USDT(TRC20)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":true,"comment":"Minimum deposit amount is 10 USDT. Only TRON main network supported", "commission_desc":"0%", "currency_confirmations":2},
        #             {"type":"withdraw", "name":"USDT(TRC20)", "currency_name":"USDT", "min":"10", "max":"150000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Only TRON main network supported.", "commission_desc":"1 USDT", "currency_confirmations":6}
        #         ],
        #         "XLM":[
        #             {"type":"deposit", "name":"XLM", "currency_name":"XLM", "min":"1", "max":"1000000", "enabled":true,"comment":"Attention! A deposit without memo(invoice) will not be credited. Minimum deposit amount is 1 XLM. We do not support BSC and BEP20 network, please consider self when sending funds", "commission_desc":"0%", "currency_confirmations":1},
        #             {"type":"withdraw", "name":"XLM", "currency_name":"XLM", "min":"21", "max":"1000000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales.", "commission_desc":"0.01 XLM", "currency_confirmations":1}
        #         ],
        #     }
        #
        result = {
            'info': cryptoList,
            'withdraw': {},
            'deposit': {},
        }
        for i in range(0, len(currencyList)):
            currency = currencyList[i]
            currencyId = self.safe_string(currency, 'name')
            code = self.safe_currency_code(currencyId)
            providers = self.safe_value(cryptoList, currencyId, [])
            for j in range(0, len(providers)):
                provider = providers[j]
                type = self.safe_string(provider, 'type')
                commissionDesc = self.safe_string(provider, 'commission_desc')
                newFee = self.parse_fixed_float_value(commissionDesc)
                previousFee = self.safe_number(result[type], code)
                if (previousFee is None) or ((newFee is not None) and (newFee < previousFee)):
                    result[type][code] = newFee
        # cache them for later use
        self.options['transactionFees'] = result
        return result

    def fetch_currencies(self, params={}):
        """
        fetches all available currencies on an exchange
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: an associative dictionary of currencies
        """
        #
        currencyList = self.publicGetCurrencyListExtended(params)
        #
        #     [
        #         {"name":"VLX","description":"Velas"},
        #         {"name":"RUB","description":"Russian Ruble"},
        #         {"name":"BTC","description":"Bitcoin"},
        #         {"name":"USD","description":"US Dollar"}
        #     ]
        #
        cryptoList = self.publicGetPaymentsProvidersCryptoList(params)
        #
        #     {
        #         "BTC":[
        #             {"type":"deposit", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.001 BTC. We do not support BSC and BEP20 network, please consider self when sending funds", "commission_desc":"0%", "currency_confirmations":1},
        #             {"type":"withdraw", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"350", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.0005 BTC", "currency_confirmations":6}
        #         ],
        #         "ETH":[
        #             {"type":"withdraw", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"500", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.004 ETH", "currency_confirmations":4},
        #             {"type":"deposit", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.01 ETH. We do not support BSC and BEP20 network, please consider self when sending funds", "commission_desc":"0%", "currency_confirmations":1}
        #         ],
        #         "USDT":[
        #             {"type":"deposit", "name":"USDT(OMNI)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":false,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2},
        #             {"type":"withdraw", "name":"USDT(OMNI)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":false,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"5 USDT", "currency_confirmations":6},
        #             {"type":"deposit", "name":"USDT(ERC20)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2},
        #             {"type":"withdraw", "name":"USDT(ERC20)", "currency_name":"USDT", "min":"55", "max":"200000", "enabled":true, "comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Recommendation: Due to the high load of ERC20 network, using TRC20 address for withdrawal is recommended.",  "commission_desc":"10 USDT", "currency_confirmations":6},
        #             {"type":"deposit", "name":"USDT(TRC20)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":true,"comment":"Minimum deposit amount is 10 USDT. Only TRON main network supported", "commission_desc":"0%", "currency_confirmations":2},
        #             {"type":"withdraw", "name":"USDT(TRC20)", "currency_name":"USDT", "min":"10", "max":"150000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Only TRON main network supported.", "commission_desc":"1 USDT", "currency_confirmations":6}
        #         ],
        #         "XLM":[
        #             {"type":"deposit", "name":"XLM", "currency_name":"XLM", "min":"1", "max":"1000000", "enabled":true,"comment":"Attention! A deposit without memo(invoice) will not be credited. Minimum deposit amount is 1 XLM. We do not support BSC and BEP20 network, please consider self when sending funds", "commission_desc":"0%", "currency_confirmations":1},
        #             {"type":"withdraw", "name":"XLM", "currency_name":"XLM", "min":"21", "max":"1000000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales.", "commission_desc":"0.01 XLM", "currency_confirmations":1}
        #         ],
        #     }
        #
        result = {}
        for i in range(0, len(currencyList)):
            currency = currencyList[i]
            currencyId = self.safe_string(currency, 'name')
            name = self.safe_string(currency, 'description')
            providers = self.safe_value(cryptoList, currencyId)
            active = False
            type = 'crypto'
            limits = {
                'deposit': {
                    'min': None,
                    'max': None,
                },
                'withdraw': {
                    'min': None,
                    'max': None,
                },
            }
            fee = None
            depositEnabled = None
            withdrawEnabled = None
            if providers is None:
                active = True
                type = 'fiat'
            else:
                for j in range(0, len(providers)):
                    provider = providers[j]
                    type = self.safe_string(provider, 'type')
                    minValue = self.safe_number(provider, 'min')
                    maxValue = self.safe_number(provider, 'max')
                    if maxValue == 0.0:
                        maxValue = None
                    activeProvider = self.safe_value(provider, 'enabled')
                    if type == 'deposit':
                        if activeProvider and not depositEnabled:
                            depositEnabled = True
                        elif not activeProvider:
                            depositEnabled = False
                    elif type == 'withdraw':
                        if activeProvider and not withdrawEnabled:
                            withdrawEnabled = True
                        elif not activeProvider:
                            withdrawEnabled = False
                    if activeProvider:
                        active = True
                        if (limits[type]['min'] is None) or (minValue < limits[type]['min']):
                            limits[type]['min'] = minValue
                            limits[type]['max'] = maxValue
                            if type == 'withdraw':
                                commissionDesc = self.safe_string(provider, 'commission_desc')
                                fee = self.parse_fixed_float_value(commissionDesc)
            code = self.safe_currency_code(currencyId)
            result[code] = {
                'id': currencyId,
                'code': code,
                'name': name,
                'type': type,
                'active': active,
                'deposit': depositEnabled,
                'withdraw': withdrawEnabled,
                'fee': fee,
                'precision': self.parse_number('0.00000001'),
                'limits': limits,
                'info': providers,
            }
        return result

    def fetch_markets(self, params={}):
        """
        retrieves data on all markets for exmo
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [dict]: an array of objects representing market data
        """
        response = self.publicGetPairSettings(params)
        #
        #     {
        #         "BTC_USD":{
        #             "min_quantity":"0.0001",
        #             "max_quantity":"1000",
        #             "min_price":"1",
        #             "max_price":"30000",
        #             "max_amount":"500000",
        #             "min_amount":"1",
        #             "price_precision":8,
        #             "commission_taker_percent":"0.4",
        #             "commission_maker_percent":"0.4"
        #         },
        #     }
        #
        keys = list(response.keys())
        result = []
        for i in range(0, len(keys)):
            id = keys[i]
            market = response[id]
            symbol = id.replace('_', '/')
            baseId, quoteId = symbol.split('/')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            takerString = self.safe_string(market, 'commission_taker_percent')
            makerString = self.safe_string(market, 'commission_maker_percent')
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': True,
                'swap': False,
                'future': False,
                'option': False,
                'active': True,
                'contract': False,
                'linear': None,
                'inverse': None,
                'taker': self.parse_number(Precise.string_div(takerString, '100')),
                'maker': self.parse_number(Precise.string_div(makerString, '100')),
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': self.parse_number('0.00000001'),
                    'price': self.parse_number(self.parse_precision(self.safe_string(market, 'price_precision'))),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': self.safe_number(market, 'min_quantity'),
                        'max': self.safe_number(market, 'max_quantity'),
                    },
                    'price': {
                        'min': self.safe_number(market, 'min_price'),
                        'max': self.safe_number(market, 'max_price'),
                    },
                    'cost': {
                        'min': self.safe_number(market, 'min_amount'),
                        'max': self.safe_number(market, 'max_amount'),
                    },
                },
                'info': market,
            })
        return result

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'resolution': self.timeframes[timeframe],
        }
        options = self.safe_value(self.options, 'fetchOHLCV')
        maxLimit = self.safe_integer(options, 'maxLimit', 3000)
        duration = self.parse_timeframe(timeframe)
        now = self.milliseconds()
        if since is None:
            if limit is None:
                limit = 1000  # cap default at generous amount
            if limit > maxLimit:
                limit = maxLimit  # avoid exception
            request['from'] = int(now / 1000) - limit * duration - 1
            request['to'] = int(now / 1000)
        else:
            request['from'] = int(since / 1000) - 1
            if limit is None:
                request['to'] = int(now / 1000)
            else:
                if limit > maxLimit:
                    raise BadRequest(self.id + ' fetchOHLCV() will serve ' + str(maxLimit) + ' candles at most')
                to = self.sum(since, limit * duration * 1000)
                request['to'] = int(to / 1000)
        response = self.publicGetCandlesHistory(self.extend(request, params))
        #
        #     {
        #         "candles":[
        #             {"t":1584057600000,"o":0.02235144,"c":0.02400233,"h":0.025171,"l":0.02221,"v":5988.34031761},
        #             {"t":1584144000000,"o":0.0240373,"c":0.02367413,"h":0.024399,"l":0.0235,"v":2027.82522329},
        #             {"t":1584230400000,"o":0.02363458,"c":0.02319242,"h":0.0237948,"l":0.02223196,"v":1707.96944997},
        #         ]
        #     }
        #
        candles = self.safe_value(response, 'candles', [])
        return self.parse_ohlcvs(candles, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "t":1584057600000,
        #         "o":0.02235144,
        #         "c":0.02400233,
        #         "h":0.025171,
        #         "l":0.02221,
        #         "v":5988.34031761
        #     }
        #
        return [
            self.safe_integer(ohlcv, 't'),
            self.safe_number(ohlcv, 'o'),
            self.safe_number(ohlcv, 'h'),
            self.safe_number(ohlcv, 'l'),
            self.safe_number(ohlcv, 'c'),
            self.safe_number(ohlcv, 'v'),
        ]

    def parse_balance(self, response):
        result = {'info': response}
        free = self.safe_value(response, 'balances', {})
        used = self.safe_value(response, 'reserved', {})
        currencyIds = list(free.keys())
        for i in range(0, len(currencyIds)):
            currencyId = currencyIds[i]
            code = self.safe_currency_code(currencyId)
            account = self.account()
            if currencyId in free:
                account['free'] = self.safe_string(free, currencyId)
            if currencyId in used:
                account['used'] = self.safe_string(used, currencyId)
            result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        self.load_markets()
        response = self.privatePostUserInfo(params)
        #
        #     {
        #         "uid":131685,
        #         "server_date":1628999600,
        #         "balances":{
        #             "EXM":"0",
        #             "USD":"0",
        #             "EUR":"0",
        #             "GBP":"0",
        #         },
        #     }
        #
        return self.parse_balance(response)

    def fetch_order_book(self, symbol, limit=None, params={}):
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'pair': market['id'],
        }
        if limit is not None:
            request['limit'] = limit
        response = self.publicGetOrderBook(self.extend(request, params))
        result = self.safe_value(response, market['id'])
        return self.parse_order_book(result, market['symbol'], None, 'bid', 'ask')

    def fetch_order_books(self, symbols=None, limit=None, params={}):
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data for multiple markets
        :param [str]|None symbols: list of unified market symbols, all symbols fetched if None, default is None
        :param int|None limit: max number of entries per orderbook to return, default is None
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbol
        """
        self.load_markets()
        ids = None
        if symbols is None:
            ids = ','.join(self.ids)
            # max URL length is 2083 symbols, including http schema, hostname, tld, etc...
            if len(ids) > 2048:
                numIds = len(self.ids)
                raise ExchangeError(self.id + ' fetchOrderBooks() has ' + str(numIds) + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks')
        else:
            ids = self.market_ids(symbols)
            ids = ','.join(ids)
        request = {
            'pair': ids,
        }
        if limit is not None:
            request['limit'] = limit
        response = self.publicGetOrderBook(self.extend(request, params))
        result = {}
        marketIds = list(response.keys())
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            symbol = self.safe_symbol(marketId)
            result[symbol] = self.parse_order_book(response[marketId], symbol, None, 'bid', 'ask')
        return result

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         "buy_price":"0.00002996",
        #         "sell_price":"0.00003002",
        #         "last_trade":"0.00002992",
        #         "high":"0.00003028",
        #         "low":"0.00002935",
        #         "avg":"0.00002963",
        #         "vol":"1196546.3163222",
        #         "vol_curr":"35.80066578",
        #         "updated":1642291733
        #     }
        #
        timestamp = self.safe_timestamp(ticker, 'updated')
        market = self.safe_market(None, market)
        last = self.safe_string(ticker, 'last_trade')
        return self.safe_ticker({
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': self.safe_string(ticker, 'buy_price'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'sell_price'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': self.safe_string(ticker, 'avg'),
            'baseVolume': self.safe_string(ticker, 'vol'),
            'quoteVolume': self.safe_string(ticker, 'vol_curr'),
            'info': ticker,
        }, market)

    def fetch_tickers(self, symbols=None, params={}):
        """
        fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
        :param [str]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: an array of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        symbols = self.market_symbols(symbols)
        response = self.publicGetTicker(params)
        #
        #     {
        #         "ADA_BTC":{
        #             "buy_price":"0.00002996",
        #             "sell_price":"0.00003002",
        #             "last_trade":"0.00002992",
        #             "high":"0.00003028",
        #             "low":"0.00002935",
        #             "avg":"0.00002963",
        #             "vol":"1196546.3163222",
        #             "vol_curr":"35.80066578",
        #             "updated":1642291733
        #         }
        #     }
        #
        result = {}
        marketIds = list(response.keys())
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            market = self.safe_market(marketId, None, '_')
            symbol = market['symbol']
            ticker = self.safe_value(response, marketId)
            result[symbol] = self.parse_ticker(ticker, market)
        return self.filter_by_array(result, 'symbol', symbols)

    def fetch_ticker(self, symbol, params={}):
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        response = self.publicGetTicker(params)
        market = self.market(symbol)
        return self.parse_ticker(response[market['id']], market)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         "trade_id":165087520,
        #         "date":1587470005,
        #         "type":"buy",
        #         "quantity":"1.004",
        #         "price":"0.02491461",
        #         "amount":"0.02501426"
        #     },
        #
        # fetchMyTrades, fetchOrderTrades
        #
        #     {
        #         "trade_id": 3,
        #         "date": 1435488248,
        #         "type": "buy",
        #         "pair": "BTC_USD",
        #         "order_id": 12345,
        #         "quantity": 1,
        #         "price": 100,
        #         "amount": 100,
        #         "exec_type": "taker",
        #         "commission_amount": "0.02",
        #         "commission_currency": "BTC",
        #         "commission_percent": "0.2"
        #     }
        #
        timestamp = self.safe_timestamp(trade, 'date')
        id = self.safe_string(trade, 'trade_id')
        orderId = self.safe_string(trade, 'order_id')
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'quantity')
        costString = self.safe_string(trade, 'amount')
        side = self.safe_string(trade, 'type')
        type = None
        marketId = self.safe_string(trade, 'pair')
        market = self.safe_market(marketId, market, '_')
        symbol = market['symbol']
        takerOrMaker = self.safe_string(trade, 'exec_type')
        fee = None
        feeCostString = self.safe_string(trade, 'commission_amount')
        if feeCostString is not None:
            feeCurrencyId = self.safe_string(trade, 'commission_currency')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            feeRateString = self.safe_string(trade, 'commission_percent')
            if feeRateString is not None:
                feeRateString = Precise.string_div(feeRateString, '1000', 18)
            fee = {
                'cost': feeCostString,
                'currency': feeCurrencyCode,
                'rate': feeRateString,
            }
        return self.safe_trade({
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': type,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': priceString,
            'amount': amountString,
            'cost': costString,
            'fee': fee,
        }, market)

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        """
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'pair': market['id'],
        }
        response = self.publicGetTrades(self.extend(request, params))
        #
        #     {
        #         "ETH_BTC":[
        #             {
        #                 "trade_id":165087520,
        #                 "date":1587470005,
        #                 "type":"buy",
        #                 "quantity":"1.004",
        #                 "price":"0.02491461",
        #                 "amount":"0.02501426"
        #             },
        #             {
        #                 "trade_id":165087369,
        #                 "date":1587469938,
        #                 "type":"buy",
        #                 "quantity":"0.94",
        #                 "price":"0.02492348",
        #                 "amount":"0.02342807"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, market['id'], [])
        return self.parse_trades(data, market, since, limit)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all trades made by the user
        :param str symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades structures to retrieve
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        # a symbol is required but it can be a single string, or a non-empty array
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument(a single symbol or an array)')
        self.load_markets()
        pair = None
        market = None
        if isinstance(symbol, list):
            numSymbols = len(symbol)
            if numSymbols < 1:
                raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a non-empty symbol array')
            marketIds = self.market_ids(symbol)
            pair = ','.join(marketIds)
        else:
            market = self.market(symbol)
            pair = market['id']
        request = {
            'pair': pair,
        }
        if limit is not None:
            request['limit'] = limit
        response = self.privatePostUserTrades(self.extend(request, params))
        result = []
        marketIds = list(response.keys())
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            resultMarket = self.safe_market(marketId, None, '_')
            items = response[marketId]
            trades = self.parse_trades(items, resultMarket, since, limit)
            result = self.array_concat(result, trades)
        return self.filter_by_since_limit(result, since, limit)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        """
        create a trade order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float|None price: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        prefix = (type + '_') if (type == 'market') else ''
        orderType = prefix + side
        orderPrice = price
        if (type == 'market') and (price is None):
            orderPrice = 0
        request = {
            'pair': market['id'],
            # 'leverage': 2,
            'quantity': self.amount_to_precision(market['symbol'], amount),
            # spot - buy, sell, market_buy, market_sell, market_buy_total, market_sell_total
            # margin - limit_buy, limit_sell, market_buy, market_sell, stop_buy, stop_sell, stop_limit_buy, stop_limit_sell, trailing_stop_buy, trailing_stop_sell
            'type': orderType,
            'price': self.price_to_precision(market['symbol'], orderPrice),
            # 'stop_price': self.price_to_precision(symbol, stopPrice),
            # 'distance': 0,  # distance for trailing stop orders
            # 'expire': 0,  # expiration timestamp in UTC timezone for the order, unless expire is 0
            # 'client_id': 123,  # optional, must be a positive integer
            # 'comment': '',  # up to 50 latin symbols, whitespaces, underscores
        }
        method = 'privatePostOrderCreate'
        clientOrderId = self.safe_value_2(params, 'client_id', 'clientOrderId')
        if clientOrderId is not None:
            clientOrderId = self.safe_integer_2(params, 'client_id', 'clientOrderId')
            if clientOrderId is None:
                raise BadRequest(self.id + ' createOrder() client order id must be an integer / numeric literal')
            else:
                request['client_id'] = clientOrderId
            params = self.omit(params, ['client_id', 'clientOrderId'])
        if (type == 'stop') or (type == 'stop_limit') or (type == 'trailing_stop'):
            stopPrice = self.safe_number_2(params, 'stop_price', 'stopPrice')
            if stopPrice is None:
                raise InvalidOrder(self.id + ' createOrder() requires a stopPrice extra param for a ' + type + ' order')
            else:
                params = self.omit(params, ['stopPrice', 'stop_price'])
                request['stop_price'] = self.price_to_precision(symbol, stopPrice)
                method = 'privatePostMarginUserOrderCreate'
        response = getattr(self, method)(self.extend(request, params))
        id = self.safe_string(response, 'order_id')
        timestamp = self.milliseconds()
        status = 'open'
        return {
            'id': id,
            'info': response,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': market['symbol'],
            'type': type,
            'side': side,
            'price': price,
            'cost': None,
            'amount': amount,
            'remaining': amount,
            'filled': 0.0,
            'fee': None,
            'trades': None,
            'clientOrderId': clientOrderId,
            'average': None,
        }

    def cancel_order(self, id, symbol=None, params={}):
        """
        cancels an open order
        :param str id: order id
        :param str|None symbol: not used by exmo cancelOrder()
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        request = {'order_id': id}
        return self.privatePostOrderCancel(self.extend(request, params))

    def fetch_order(self, id, symbol=None, params={}):
        """
        fetches information on an order made by the user
        :param str|None symbol: not used by exmo fetchOrder
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        request = {
            'order_id': str(id),
        }
        response = self.privatePostOrderTrades(self.extend(request, params))
        #
        #     {
        #         "type": "buy",
        #         "in_currency": "BTC",
        #         "in_amount": "1",
        #         "out_currency": "USD",
        #         "out_amount": "100",
        #         "trades": [
        #             {
        #                 "trade_id": 3,
        #                 "date": 1435488248,
        #                 "type": "buy",
        #                 "pair": "BTC_USD",
        #                 "order_id": 12345,
        #                 "quantity": 1,
        #                 "price": 100,
        #                 "amount": 100
        #             }
        #         ]
        #     }
        #
        order = self.parse_order(response)
        return self.extend(order, {
            'id': str(id),
        })

    def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        """
        fetch all the trades made from a single order
        :param str id: order id
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades to retrieve
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request = {
            'order_id': str(id),
        }
        response = self.privatePostOrderTrades(self.extend(request, params))
        #
        #     {
        #         "type": "buy",
        #         "in_currency": "BTC",
        #         "in_amount": "1",
        #         "out_currency": "USD",
        #         "out_amount": "100",
        #         "trades": [
        #             {
        #                 "trade_id": 3,
        #                 "date": 1435488248,
        #                 "type": "buy",
        #                 "pair": "BTC_USD",
        #                 "order_id": 12345,
        #                 "quantity": 1,
        #                 "price": 100,
        #                 "amount": 100,
        #                 "exec_type": "taker",
        #                 "commission_amount": "0.02",
        #                 "commission_currency": "BTC",
        #                 "commission_percent": "0.2"
        #             }
        #         ]
        #     }
        #
        trades = self.safe_value(response, 'trades')
        return self.parse_trades(trades, market, since, limit)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all unfilled currently open orders
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch open orders for
        :param int|None limit: the maximum number of  open orders structures to retrieve
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
        response = self.privatePostUserOpenOrders(params)
        marketIds = list(response.keys())
        orders = []
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            market = self.safe_market(marketId)
            parsedOrders = self.parse_orders(response[marketId], market)
            orders = self.array_concat(orders, parsedOrders)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit)

    def parse_order(self, order, market=None):
        #
        # fetchOrders, fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders
        #
        #     {
        #         "order_id": "14",
        #         "created": "1435517311",
        #         "type": "buy",
        #         "pair": "BTC_USD",
        #         "price": "100",
        #         "quantity": "1",
        #         "amount": "100"
        #     }
        #
        # fetchOrder
        #
        #     {
        #         "type": "buy",
        #         "in_currency": "BTC",
        #         "in_amount": "1",
        #         "out_currency": "USD",
        #         "out_amount": "100",
        #         "trades": [
        #             {
        #                 "trade_id": 3,
        #                 "date": 1435488248,
        #                 "type": "buy",
        #                 "pair": "BTC_USD",
        #                 "order_id": 12345,
        #                 "quantity": 1,
        #                 "price": 100,
        #                 "amount": 100
        #             }
        #         ]
        #     }
        #
        id = self.safe_string(order, 'order_id')
        timestamp = self.safe_timestamp(order, 'created')
        symbol = None
        side = self.safe_string(order, 'type')
        marketId = None
        if 'pair' in order:
            marketId = order['pair']
        elif ('in_currency' in order) and ('out_currency' in order):
            if side == 'buy':
                marketId = order['in_currency'] + '_' + order['out_currency']
            else:
                marketId = order['out_currency'] + '_' + order['in_currency']
        market = self.safe_market(marketId, market)
        amount = self.safe_number(order, 'quantity')
        if amount is None:
            amountField = 'in_amount' if (side == 'buy') else 'out_amount'
            amount = self.safe_number(order, amountField)
        price = self.safe_number(order, 'price')
        cost = self.safe_number(order, 'amount')
        filled = 0.0
        trades = []
        transactions = self.safe_value(order, 'trades', [])
        feeCost = None
        lastTradeTimestamp = None
        average = None
        numTransactions = len(transactions)
        if numTransactions > 0:
            feeCost = 0
            for i in range(0, numTransactions):
                trade = self.parse_trade(transactions[i], market)
                if id is None:
                    id = trade['order']
                if timestamp is None:
                    timestamp = trade['timestamp']
                if timestamp > trade['timestamp']:
                    timestamp = trade['timestamp']
                filled = self.sum(filled, trade['amount'])
                feeCost = self.sum(feeCost, trade['fee']['cost'])
                trades.append(trade)
            lastTradeTimestamp = trades[numTransactions - 1]['timestamp']
        status = self.safe_string(order, 'status')  # in case we need to redefine it for canceled orders
        remaining = None
        if amount is not None:
            remaining = amount - filled
            if filled >= amount:
                status = 'closed'
            else:
                status = 'open'
        if market is None:
            market = self.get_market_from_trades(trades)
        feeCurrency = None
        if market is not None:
            symbol = market['symbol']
            feeCurrency = market['quote']
        if cost is None:
            if price is not None:
                cost = price * filled
        else:
            if filled > 0:
                if average is None:
                    average = cost / filled
                if price is None:
                    price = cost / filled
        fee = {
            'cost': feeCost,
            'currency': feeCurrency,
        }
        clientOrderId = self.safe_integer(order, 'client_id')
        return {
            'id': id,
            'clientOrderId': clientOrderId,
            'datetime': self.iso8601(timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': lastTradeTimestamp,
            'status': status,
            'symbol': symbol,
            'type': 'limit',
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'average': average,
            'trades': trades,
            'fee': fee,
            'info': order,
        }

    def fetch_canceled_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches information on multiple canceled orders made by the user
        :param str|None symbol: unified market symbol of the market orders were made in
        :param int|None since: timestamp in ms of the earliest order, default is None
        :param int|None limit: max number of orders to return, default is None
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        request = {}
        if since is not None:
            request['offset'] = limit
        if limit is not None:
            request['limit'] = limit
        market = None
        if symbol is not None:
            market = self.market(symbol)
        response = self.privatePostUserCancelledOrders(self.extend(request, params))
        #
        #     [{
        #         "order_id": "27056153840",
        #         "client_id": "0",
        #         "created": "1653428646",
        #         "type": "buy",
        #         "pair": "BTC_USDT",
        #         "quantity": "0.1",
        #         "price": "10",
        #         "amount": "1"
        #     }]
        #
        return self.parse_orders(response, market, since, limit, params)

    def fetch_deposit_address(self, code, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :param str code: unified currency code
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/en/latest/manual.html#address-structure>`
        """
        self.load_markets()
        response = self.privatePostDepositAddress(params)
        #
        #     {
        #         "TRX":"TBnwrf4ZdoYXE3C8L2KMs7YPSL3fg6q6V9",
        #         "USDTTRC20":"TBnwrf4ZdoYXE3C8L2KMs7YPSL3fg6q6V9"
        #     }
        #
        depositAddress = self.safe_string(response, code)
        address = None
        tag = None
        if depositAddress:
            addressAndTag = depositAddress.split(',')
            address = addressAndTag[0]
            numParts = len(addressAndTag)
            if numParts > 1:
                tag = addressAndTag[1]
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': None,
            'info': response,
        }

    def get_market_from_trades(self, trades):
        tradesBySymbol = self.index_by(trades, 'pair')
        symbols = list(tradesBySymbol.keys())
        numSymbols = len(symbols)
        if numSymbols == 1:
            return self.markets[symbols[0]]
        return None

    def withdraw(self, code, amount, address, tag=None, params={}):
        """
        make a withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str|None tag:
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.load_markets()
        currency = self.currency(code)
        request = {
            'amount': amount,
            'currency': currency['id'],
            'address': address,
        }
        if tag is not None:
            request['invoice'] = tag
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
        if network is not None:
            request['transport'] = network
            params = self.omit(params, 'network')
        response = self.privatePostWithdrawCrypt(self.extend(request, params))
        return self.parse_transaction(response, currency)

    def parse_transaction_status(self, status):
        statuses = {
            'transferred': 'ok',
            'paid': 'ok',
            'pending': 'pending',
            'processing': 'pending',
            'verifying': 'pending',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchTransactions
        #
        #          {
        #            "dt": 1461841192,
        #            "type": "deposit",
        #            "curr": "RUB",
        #            "status": "processing",
        #            "provider": "Qiwi(LA) [12345]",
        #            "amount": "1",
        #            "account": "",
        #            "txid": "ec46f784ad976fd7f7539089d1a129fe46...",
        #          }
        #
        # fetchWithdrawals
        #
        #          {
        #             "operation_id": 47412538520634344,
        #             "created": 1573760013,
        #             "updated": 1573760013,
        #             "type": "withdraw",
        #             "currency": "DOGE",
        #             "status": "Paid",
        #             "amount": "300",
        #             "provider": "DOGE",
        #             "commission": "0",
        #             "account": "DOGE: DBVy8pF1f8yxaCVEHqHeR7kkcHecLQ8nRS",
        #             "order_id": 69670170,
        #             "provider_type": "crypto",
        #             "crypto_address": "DBVy8pF1f8yxaCVEHqHeR7kkcHecLQ8nRS",
        #             "card_number": "",
        #             "wallet_address": "",
        #             "email": "",
        #             "phone": "",
        #             "extra": {
        #                 "txid": "f2b66259ae1580f371d38dd27e31a23fff8c04122b65ee3ab5a3f612d579c792",
        #                 "confirmations": null,
        #                 "excode": "",
        #                 "invoice": ""
        #             },
        #             "error": ""
        #          },
        #
        id = self.safe_string_2(transaction, 'order_id', 'task_id')
        timestamp = self.safe_timestamp_2(transaction, 'dt', 'created')
        updated = self.safe_timestamp(transaction, 'updated')
        amount = self.safe_string(transaction, 'amount')
        if amount is not None:
            amount = Precise.string_abs(amount)
        status = self.parse_transaction_status(self.safe_string_lower(transaction, 'status'))
        txid = self.safe_string(transaction, 'txid')
        if txid is None:
            extra = self.safe_value(transaction, 'extra', {})
            extraTxid = self.safe_string(extra, 'txid')
            if extraTxid != '':
                txid = extraTxid
        type = self.safe_string(transaction, 'type')
        currencyId = self.safe_string_2(transaction, 'curr', 'currency')
        code = self.safe_currency_code(currencyId, currency)
        address = None
        tag = None
        comment = None
        account = self.safe_string(transaction, 'account')
        if type == 'deposit':
            comment = account
        elif type == 'withdrawal':
            address = account
            if address is not None:
                parts = address.split(':')
                numParts = len(parts)
                if numParts == 2:
                    address = self.safe_string(parts, 1)
                    address = address.replace(' ', '')
        fee = None
        # fixed funding fees only(for now)
        if not self.fees['transaction']['percentage']:
            key = 'withdraw' if (type == 'withdrawal') else 'deposit'
            feeCost = self.safe_string(transaction, 'commission')
            if feeCost is None:
                feeCost = self.safe_string(self.options['transactionFees'][key], code)
            # users don't pay for cashbacks, no fees for that
            provider = self.safe_string(transaction, 'provider')
            if provider == 'cashback':
                feeCost = '0'
            if feeCost is not None:
                # withdrawal amount includes the fee
                if type == 'withdrawal':
                    amount = Precise.string_sub(amount, feeCost)
                fee = {
                    'cost': self.parse_number(feeCost),
                    'currency': code,
                    'rate': None,
                }
        network = self.safe_string(transaction, 'provider')
        return {
            'info': transaction,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'currency': code,
            'amount': amount,
            'network': network,
            'address': address,
            'addressTo': address,
            'addressFrom': None,
            'tag': tag,
            'tagTo': tag,
            'tagFrom': None,
            'status': status,
            'type': type,
            'updated': updated,
            'comment': comment,
            'txid': txid,
            'fee': fee,
        }

    def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        """
        fetch history of deposits and withdrawals
        :param str|None code: unified currency code for the currency of the transactions, default is None
        :param int|None since: timestamp in ms of the earliest transaction, default is None
        :param int|None limit: max number of transactions to return, default is None
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a list of `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        self.load_markets()
        request = {}
        if since is not None:
            request['date'] = int(since / 1000)
        currency = None
        if code is not None:
            currency = self.currency(code)
        response = self.privatePostWalletHistory(self.extend(request, params))
        #
        #     {
        #       "result": True,
        #       "error": "",
        #       "begin": "1493942400",
        #       "end": "1494028800",
        #       "history": [
        #          {
        #            "dt": 1461841192,
        #            "type": "deposit",
        #            "curr": "RUB",
        #            "status": "processing",
        #            "provider": "Qiwi(LA) [12345]",
        #            "amount": "1",
        #            "account": "",
        #            "txid": "ec46f784ad976fd7f7539089d1a129fe46...",
        #          },
        #          {
        #            "dt": 1463414785,
        #            "type": "withdrawal",
        #            "curr": "USD",
        #            "status": "paid",
        #            "provider": "EXCODE",
        #            "amount": "-1",
        #            "account": "EX-CODE_19371_USDda...",
        #            "txid": "",
        #          },
        #       ],
        #     }
        #
        return self.parse_transactions(response['history'], currency, since, limit)

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        """
        fetch all withdrawals made from an account
        :param str|None code: unified currency code
        :param int|None since: the earliest time in ms to fetch withdrawals for
        :param int|None limit: the maximum number of withdrawals structures to retrieve
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        self.load_markets()
        currency = None
        request = {
            'type': 'withdraw',
        }
        if limit is not None:
            request['limit'] = limit  # default: 100, maximum: 100
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        response = self.privatePostWalletOperations(self.extend(request, params))
        #
        #     {
        #         "items": [
        #         {
        #             "operation_id": 47412538520634344,
        #             "created": 1573760013,
        #             "updated": 1573760013,
        #             "type": "withdraw",
        #             "currency": "DOGE",
        #             "status": "Paid",
        #             "amount": "300",
        #             "provider": "DOGE",
        #             "commission": "0",
        #             "account": "DOGE: DBVy8pF1f8yxaCVEHqHeR7kkcHecLQ8nRS",
        #             "order_id": 69670170,
        #             "extra": {
        #                 "txid": "f2b66259ae1580f371d38dd27e31a23fff8c04122b65ee3ab5a3f612d579c792",
        #                 "excode": "",
        #                 "invoice": ""
        #             },
        #             "error": ""
        #         },
        #     ],
        #         "count": 23
        #     }
        #
        items = self.safe_value(response, 'items', [])
        return self.parse_transactions(items, currency, since, limit)

    def fetch_withdrawal(self, id, code=None, params={}):
        """
        fetch data on a currency withdrawal via the withdrawal id
        :param str id: withdrawal id
        :param str|None code: unified currency code of the currency withdrawn, default is None
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        self.load_markets()
        currency = None
        request = {
            'order_id': id,
            'type': 'withdraw',
        }
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        response = self.privatePostWalletOperations(self.extend(request, params))
        #
        #     {
        #         "items": [
        #         {
        #             "operation_id": 47412538520634344,
        #             "created": 1573760013,
        #             "updated": 1573760013,
        #             "type": "deposit",
        #             "currency": "DOGE",
        #             "status": "Paid",
        #             "amount": "300",
        #             "provider": "DOGE",
        #             "commission": "0",
        #             "account": "DOGE: DBVy8pF1f8yxaCVEHqHeR7kkcHecLQ8nRS",
        #             "order_id": 69670170,
        #             "extra": {
        #                 "txid": "f2b66259ae1580f371d38dd27e31a23fff8c04122b65ee3ab5a3f612d579c792",
        #                 "excode": "",
        #                 "invoice": ""
        #             },
        #             "error": ""
        #         },
        #     ],
        #         "count": 23
        #     }
        #
        items = self.safe_value(response, 'items', [])
        first = self.safe_value(items, 0, {})
        return self.parse_transaction(first, currency)

    def fetch_deposit(self, id=None, code=None, params={}):
        """
        fetch information on a deposit
        :param str id: deposit id
        :param str|None code: unified currency code, default is None
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        self.load_markets()
        currency = None
        request = {
            'order_id': id,
            'type': 'deposit',
        }
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        response = self.privatePostWalletOperations(self.extend(request, params))
        #
        #     {
        #         "items": [
        #         {
        #             "operation_id": 47412538520634344,
        #             "created": 1573760013,
        #             "updated": 1573760013,
        #             "type": "deposit",
        #             "currency": "DOGE",
        #             "status": "Paid",
        #             "amount": "300",
        #             "provider": "DOGE",
        #             "commission": "0",
        #             "account": "DOGE: DBVy8pF1f8yxaCVEHqHeR7kkcHecLQ8nRS",
        #             "order_id": 69670170,
        #             "extra": {
        #                 "txid": "f2b66259ae1580f371d38dd27e31a23fff8c04122b65ee3ab5a3f612d579c792",
        #                 "excode": "",
        #                 "invoice": ""
        #             },
        #             "error": ""
        #         },
        #     ],
        #         "count": 23
        #     }
        #
        items = self.safe_value(response, 'items', [])
        first = self.safe_value(items, 0, {})
        return self.parse_transaction(first, currency)

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        """
        fetch all deposits made to an account
        :param str|None code: unified currency code
        :param int|None since: the earliest time in ms to fetch deposits for
        :param int|None limit: the maximum number of deposits structures to retrieve
        :param dict params: extra parameters specific to the exmo api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        self.load_markets()
        currency = None
        request = {
            'type': 'deposit',
        }
        if limit is not None:
            request['limit'] = limit  # default: 100, maximum: 100
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        response = self.privatePostWalletOperations(self.extend(request, params))
        #
        #     {
        #         "items": [
        #         {
        #             "operation_id": 47412538520634344,
        #             "created": 1573760013,
        #             "updated": 1573760013,
        #             "type": "deposit",
        #             "currency": "DOGE",
        #             "status": "Paid",
        #             "amount": "300",
        #             "provider": "DOGE",
        #             "commission": "0",
        #             "account": "DOGE: DBVy8pF1f8yxaCVEHqHeR7kkcHecLQ8nRS",
        #             "order_id": 69670170,
        #             "extra": {
        #                 "txid": "f2b66259ae1580f371d38dd27e31a23fff8c04122b65ee3ab5a3f612d579c792",
        #                 "excode": "",
        #                 "invoice": ""
        #             },
        #             "error": ""
        #         },
        #     ],
        #         "count": 23
        #     }
        #
        items = self.safe_value(response, 'items', [])
        return self.parse_transactions(items, currency, since, limit)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api] + '/'
        if api != 'web':
            url += self.version + '/'
        url += path
        if (api == 'public') or (api == 'web'):
            if params:
                url += '?' + self.urlencode(params)
        elif api == 'private':
            self.check_required_credentials()
            nonce = self.nonce()
            body = self.urlencode(self.extend({'nonce': nonce}, params))
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Key': self.apiKey,
                'Sign': self.hmac(self.encode(body), self.encode(self.secret), hashlib.sha512),
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

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

    def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return  # fallback to default error handler
        if ('result' in response) or ('errmsg' in response):
            #
            #     {"result":false,"error":"Error 50052: Insufficient funds"}
            #     {"s":"error","errmsg":"strconv.ParseInt: parsing \"\": invalid syntax"}
            #
            success = self.safe_value(response, 'result', False)
            if isinstance(success, str):
                if (success == 'true') or (success == '1'):
                    success = True
                else:
                    success = False
            if not success:
                code = None
                message = self.safe_string_2(response, 'error', 'errmsg')
                errorParts = message.split(':')
                numParts = len(errorParts)
                if numParts > 1:
                    errorSubParts = errorParts[0].split(' ')
                    numSubParts = len(errorSubParts)
                    code = errorSubParts[1] if (numSubParts > 1) else errorSubParts[0]
                feedback = self.id + ' ' + body
                self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
                self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
                raise ExchangeError(feedback)
