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

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

from ccxt_zagrava.async_support.base.exchange import Exchange
from ccxt_zagrava.abstract.exmo import ImplicitAPI
import hashlib
from ccxt_zagrava.base.types import Balances, Currencies, Currency, Int, MarginModification, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFees, Transaction
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import PermissionDenied
from ccxt_zagrava.base.errors import ArgumentsRequired
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import InsufficientFunds
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import OrderNotFound
from ccxt_zagrava.base.errors import RateLimitExceeded
from ccxt_zagrava.base.errors import OnMaintenance
from ccxt_zagrava.base.errors import InvalidNonce
from ccxt_zagrava.base.decimal_to_precision import TICK_SIZE
from ccxt_zagrava.base.precise import Precise


class exmo(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(exmo, self).describe(), {
            'id': 'exmo',
            'name': 'EXMO',
            'countries': ['LT'],  # Lithuania
            'rateLimit': 100,  # 10 requests per 1 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,
                'editOrder': True,  # margin only
                'fetchAccounts': False,
                'fetchBalance': True,
                'fetchCanceledOrders': True,
                'fetchCurrencies': True,
                'fetchDeposit': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchDepositsWithdrawals': True,
                'fetchDepositWithdrawFee': 'emulated',
                'fetchDepositWithdrawFees': 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,
                'fetchPosition': False,
                'fetchPositionHistory': False,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPositionsHistory': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': True,
                'fetchTransactionFees': True,
                'fetchTransactions': 'emulated',
                '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 fetchDepositWithdrawFees below
                },
            },
            'options': {
                'networks': {
                    'ETH': 'ERC20',
                    'TRX': 'TRC20',
                },
                'fetchTradingFees': {
                    'method': 'fetchPrivateTradingFees',  # or 'fetchPublicTradingFees'
                },
                'margin': {
                    'fillResponseFromRequest': True,
                },
            },
            'commonCurrencies': {
                'GMT': 'GMT Token',
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    '140434': BadRequest,
                    '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}
                },
            },
        })

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

    def parse_margin_modification(self, data: dict, market: Market = None) -> MarginModification:
        #
        #      {}
        #
        return {
            'info': data,
            'symbol': self.safe_symbol(None, market),
            'type': None,
            'marginMode': 'isolated',
            'amount': None,
            'total': None,
            'code': self.safe_value(market, 'quote'),
            'status': 'ok',
            'timestamp': None,
            'datetime': None,
        }

    async def reduce_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
        """
        remove margin from a position
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#eebf9f25-0289-4946-9482-89872c738449
        :param str symbol: unified market symbol
        :param float amount: the amount of margin to remove
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `margin structure <https://docs.ccxt_zagrava.com/#/?id=reduce-margin-structure>`
        """
        return await self.modify_margin_helper(symbol, amount, 'reduce', params)

    async def add_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
        """
        add margin
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#143ef808-79ca-4e49-9e79-a60ea4d8c0e3
        :param str symbol: unified market symbol
        :param float amount: amount of margin to add
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `margin structure <https://docs.ccxt_zagrava.com/#/?id=add-margin-structure>`
        """
        return await self.modify_margin_helper(symbol, amount, 'add', params)

    async def fetch_trading_fees(self, params={}) -> TradingFees:
        """
        fetch the trading fees for multiple markets
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#90927062-256c-4b03-900f-2b99131f9a54
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#7de7e75c-5833-45a8-b937-c2276d235aaa
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `fee structures <https://docs.ccxt_zagrava.com/#/?id=fee-structure>` indexed by market symbols
        """
        options = self.safe_value(self.options, 'fetchTradingFees', {})
        defaultMethod = self.safe_string(options, 'method', 'fetchPrivateTradingFees')
        method = self.safe_string(params, 'method', defaultMethod)
        params = self.omit(params, 'method')
        if method == 'fetchPrivateTradingFees':
            return await self.fetch_private_trading_fees(params)
        else:
            return await self.fetch_public_trading_fees(params)

    async def fetch_private_trading_fees(self, params={}):
        await self.load_markets()
        response = await 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: dict = {}
        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

    async def fetch_public_trading_fees(self, params={}):
        await self.load_markets()
        response = await 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: dict = {}
        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

    async def fetch_transaction_fees(self, codes: Strings = None, params={}):
        """
         * @deprecated
        please use fetchDepositWithdrawFees instead
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#4190035d-24b1-453d-833b-37e0a52f88e2
        :param str[]|None codes: list of unified currency codes
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a list of `transaction fees structures <https://docs.ccxt_zagrava.com/#/?id=fees-structure>`
        """
        await self.load_markets()
        cryptoList = await 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 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 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 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, 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, 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, account will not be credited with tokens from such sales.", "commission_desc":"0.01 XLM", "currency_confirmations":1}
        #         ],
        #     }
        #
        result: dict = {}
        cryptoListKeys = list(cryptoList.keys())
        for i in range(0, len(cryptoListKeys)):
            code = cryptoListKeys[i]
            if codes is not None and not self.in_array(code, codes):
                continue
            result[code] = {
                'deposit': None,
                'withdraw': None,
            }
            currency = self.currency(code)
            currencyId = self.safe_string(currency, 'id')
            providers = self.safe_value(cryptoList, currencyId, [])
            for j in range(0, len(providers)):
                provider = providers[j]
                typeInner = self.safe_string(provider, 'type')
                commissionDesc = self.safe_string(provider, 'commission_desc')
                fee = self.parse_fixed_float_value(commissionDesc)
                result[code][typeInner] = fee
            result[code]['info'] = providers
        # cache them for later use
        self.options['transactionFees'] = result
        return result

    async def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
        """
        fetch deposit and withdraw fees
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#4190035d-24b1-453d-833b-37e0a52f88e2
        :param str[]|None codes: list of unified currency codes
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a list of `transaction fees structures <https://docs.ccxt_zagrava.com/#/?id=fees-structure>`
        """
        await self.load_markets()
        response = await self.publicGetPaymentsProvidersCryptoList(params)
        #
        #    {
        #        "USDT": [
        #            {
        #                "type": "deposit",  # or "withdraw"
        #                "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
        #            },
        #            ...
        #        ],
        #        ...
        #    }
        #
        result = self.parse_deposit_withdraw_fees(response, codes)
        # cache them for later use
        self.options['transactionFees'] = result
        return result

    def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
        #
        #    [
        #        {
        #            "type": "deposit",  # or "withdraw"
        #            "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
        #        },
        #        ...
        #    ]
        #
        result = self.deposit_withdraw_fee(fee)
        for i in range(0, len(fee)):
            provider = fee[i]
            type = self.safe_string(provider, 'type')
            networkId = self.safe_string(provider, 'name')
            networkCode = self.network_id_to_code(networkId, self.safe_string(currency, 'code'))
            commissionDesc = self.safe_string(provider, 'commission_desc')
            splitCommissionDesc = []
            percentage = None
            if commissionDesc is not None:
                splitCommissionDesc = commissionDesc.split('%')
                splitCommissionDescLength = len(splitCommissionDesc)
                percentage = splitCommissionDescLength >= 2
            network = self.safe_value(result['networks'], networkCode)
            if network is None:
                result['networks'][networkCode] = {
                    'withdraw': {
                        'fee': None,
                        'percentage': None,
                    },
                    'deposit': {
                        'fee': None,
                        'percentage': None,
                    },
                }
            result['networks'][networkCode][type] = {
                'fee': self.parse_fixed_float_value(self.safe_string(splitCommissionDesc, 0)),
                'percentage': percentage,
            }
        return self.assign_default_deposit_withdraw_fees(result)

    async def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#7cdf0ca8-9ff6-4cf3-aa33-bcec83155c49
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#4190035d-24b1-453d-833b-37e0a52f88e2
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        #
        currencyList = await self.publicGetCurrencyListExtended(params)
        #
        #     [
        #         {"name":"VLX","description":"Velas"},
        #         {"name":"RUB","description":"Russian Ruble"},
        #         {"name":"BTC","description":"Bitcoin"},
        #         {"name":"USD","description":"US Dollar"}
        #     ]
        #
        cryptoList = await 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 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 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 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, 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, 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, account will not be credited with tokens from such sales.", "commission_desc":"0.01 XLM", "currency_confirmations":1}
        #         ],
        #     }
        #
        result: dict = {}
        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: dict = {
                '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]
                    typeInner = self.safe_string(provider, 'type')
                    minValue = self.safe_string(provider, 'min')
                    maxValue = self.safe_string(provider, 'max')
                    if Precise.string_eq(maxValue, '0.0'):
                        maxValue = None
                    activeProvider = self.safe_value(provider, 'enabled')
                    if typeInner == 'deposit':
                        if activeProvider and not depositEnabled:
                            depositEnabled = True
                        elif not activeProvider:
                            depositEnabled = False
                    elif typeInner == 'withdraw':
                        if activeProvider and not withdrawEnabled:
                            withdrawEnabled = True
                        elif not activeProvider:
                            withdrawEnabled = False
                    if activeProvider:
                        active = True
                        limitMin = self.number_to_string(limits[typeInner]['min'])
                        if (limits[typeInner]['min'] is None) or (Precise.string_lt(minValue, limitMin)):
                            limits[typeInner]['min'] = minValue
                            limits[typeInner]['max'] = maxValue
                            if typeInner == '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('1e-8'),
                'limits': limits,
                'info': providers,
                'networks': {},
            }
        return result

    async def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for exmo
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#7de7e75c-5833-45a8-b937-c2276d235aaa
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = await 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"
        #         },
        #     }
        #
        marginPairsDict: dict = {}
        if self.check_required_credentials(False):
            marginPairs = await self.privatePostMarginPairList(params)
            #
            #    {
            #        "pairs": [
            #            {
            #                "buy_price": "55978.85",
            #                "default_leverage": "3",
            #                "is_fair_price": True,
            #                "last_trade_price": "55999.23",
            #                "liquidation_fee": "2",
            #                "liquidation_level": "10",
            #                "margin_call_level": "15",
            #                "max_leverage": "3",
            #                "max_order_price": "150000",
            #                "max_order_quantity": "1",
            #                "max_position_quantity": "1",
            #                "max_price_precision": 2,
            #                "min_order_price": "1",
            #                "min_order_quantity": "0.00002",
            #                "name": "BTC_USD",
            #                "position": 1,
            #                "sell_price": "55985.51",
            #                "ticker_updated": "1619019818936107989",
            #                "trade_maker_fee": "0",
            #                "trade_taker_fee": "0.05",
            #                "updated": "1619008608955599013"
            #            }
            #        ]
            #    }
            #
            pairs = self.safe_value(marginPairs, 'pairs')
            marginPairsDict = self.index_by(pairs, 'name')
        keys = list(response.keys())
        result = []
        for i in range(0, len(keys)):
            id = keys[i]
            market = response[id]
            marginMarket = self.safe_value(marginPairsDict, 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')
            maxQuantity = self.safe_string(market, 'max_quantity')
            marginMaxQuantity = self.safe_string(marginMarket, 'max_order_quantity')
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': marginMarket is not None,
                'swap': False,
                'future': False,
                'option': False,
                'active': None,
                '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('1e-8'),
                    'price': self.parse_number(self.parse_precision(self.safe_string(market, 'price_precision'))),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': self.safe_number(market, 'leverage'),
                    },
                    'amount': {
                        'min': self.safe_number(market, 'min_quantity'),
                        'max': self.parse_number(Precise.string_max(maxQuantity, marginMaxQuantity)),
                    },
                    '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'),
                    },
                },
                'created': None,
                'info': market,
            })
        return result

    async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#65eeb949-74e5-4631-9184-c38387fe53e8
        :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 [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
            'resolution': self.safe_string(self.timeframes, timeframe, timeframe),
        }
        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
            else:
                limit = min(limit, maxLimit)
            request['from'] = self.parse_to_int(now / 1000) - limit * duration - 1
            request['to'] = self.parse_to_int(now / 1000)
        else:
            request['from'] = self.parse_to_int(since / 1000) - 1
            if limit is None:
                limit = maxLimit
            else:
                limit = min(limit, maxLimit)
            to = self.sum(since, limit * duration * 1000)
            request['to'] = self.parse_to_int(to / 1000)
        response = await 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_list(response, 'candles', [])
        return self.parse_ohlcvs(candles, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        #     {
        #         "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) -> Balances:
        result: dict = {'info': response}
        wallets = self.safe_value(response, 'wallets')
        if wallets is not None:
            currencyIds = list(wallets.keys())
            for i in range(0, len(currencyIds)):
                currencyId = currencyIds[i]
                item = wallets[currencyId]
                currency = self.safe_currency_code(currencyId)
                account = self.account()
                account['used'] = self.safe_string(item, 'used')
                account['free'] = self.safe_string(item, 'free')
                account['total'] = self.safe_string(item, 'balance')
                result[currency] = account
        else:
            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)

    async def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#59c5160f-27a1-4d9a-8cfb-7979c7ffaac6
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#c8388df7-1f9f-4d41-81c4-5a387d171dc6
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.marginMode]: *isolated* fetches the isolated margin balance
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('fetchBalance', params)
        if marginMode == 'cross':
            raise BadRequest(self.id + ' does not support cross margin')
        response = None
        if marginMode == 'isolated':
            response = await self.privatePostMarginUserWalletList(params)
            #
            #    {
            #        "wallets": {
            #            "USD": {
            #                "balance": "1000",
            #                "free": "600",
            #                "used": "400"
            #            }
            #        }
            #    }
            #
        else:
            response = await self.privatePostUserInfo(params)
            #
            #     {
            #         "uid":131685,
            #         "server_date":1628999600,
            #         "balances":{
            #             "EXM":"0",
            #             "USD":"0",
            #             "EUR":"0",
            #             "GBP":"0",
            #         },
            #     }
            #
        return self.parse_balance(response)

    async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#c60c51a8-e683-4f45-a000-820723d37871
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
        }
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetOrderBook(self.extend(request, params))
        result = self.safe_dict(response, market['id'])
        return self.parse_order_book(result, market['symbol'], None, 'bid', 'ask')

    async def fetch_order_books(self, symbols: Strings = None, limit: Int = None, params={}):
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data for multiple markets
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#c60c51a8-e683-4f45-a000-820723d37871
        :param str[]|None symbols: list of unified market symbols, all symbols fetched if None, default is None
        :param int [limit]: max number of entries per orderbook to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbol
        """
        await 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: dict = {
            'pair': ids,
        }
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetOrderBook(self.extend(request, params))
        result: dict = {}
        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: dict, market: Market = None) -> Ticker:
        #
        #     {
        #         "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)

    async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#4c8e6459-3503-4361-b012-c34bb9f7e385
        :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 exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        response = await 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: dict = {}
        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_tickers(result, 'symbol', symbols)

    async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#4c8e6459-3503-4361-b012-c34bb9f7e385
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        response = await self.publicGetTicker(params)
        market = self.market(symbol)
        return self.parse_ticker(response[market['id']], market)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # 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"
        #     }
        #
        # fetchMyTrades(margin)
        #
        #    {
        #        "trade_id": "692861757015952517",
        #        "trade_dt": "1693951853197811824",
        #        "trade_type": "buy",
        #        "pair": "ADA_USDT",
        #        "quantity": "1.96607879",
        #        "price": "0.2568",
        #        "amount": "0.50488903"
        #    }
        #
        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_2(trade, 'type', 'trade_type')
        type = None
        marketId = self.safe_string(trade, 'pair')
        market = self.safe_market(marketId, market, '_')
        symbol = market['symbol']
        isMaker = self.safe_value(trade, 'is_maker')
        takerOrMakerDefault = None
        if isMaker is not None:
            takerOrMakerDefault = 'maker' if isMaker else 'taker'
        takerOrMaker = self.safe_string(trade, 'exec_type', takerOrMakerDefault)
        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)

    async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#5a5a9c0d-cf17-47f6-9d62-6d4404ebd5ac
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
        }
        response = await 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_list(response, market['id'], [])
        return self.parse_trades(data, market, since, limit)

    async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#b8d8d9af-4f46-46a1-939b-ad261d79f452  # spot
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#f4b1aaf8-399f-403b-ab5e-4926d967a106  # margin
        :param str symbol: a symbol is required but it can be a single string, or a non-empty array
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: *required for margin orders* the maximum number of trades structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
         *
         * EXCHANGE SPECIFIC PARAMETERS
        :param int [params.offset]: last deal offset, default = 0
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=trade-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('fetchMyTrades', params)
        if marginMode == 'cross':
            raise BadRequest(self.id + 'only isolated margin is supported')
        await self.load_markets()
        market = self.market(symbol)
        pair = market['id']
        isSpot = marginMode != 'isolated'
        if limit is None:
            limit = 100
        request: dict = {}
        if isSpot:
            request['pair'] = pair
        else:
            request['pair_name'] = pair
        if limit is not None:
            request['limit'] = limit
        offset = self.safe_integer(params, 'offset', 0)
        request['offset'] = offset
        response = None
        if isSpot:
            response = await self.privatePostUserTrades(self.extend(request, params))
            #
            #    {
            #        "BTC_USD": [
            #            {
            #                "trade_id": 20056872,
            #                "client_id": 100500,
            #                "date": 1435488248,
            #                "type": "buy",
            #                "pair": "BTC_USD",
            #                "quantity": "1",
            #                "price": "100",
            #                "amount": "100",
            #                "order_id": 7,
            #                "parent_order_id": 117684023830293,
            #                "exec_type": "taker",
            #                "commission_amount": "0.02",
            #                "commission_currency": "BTC",
            #                "commission_percent": "0.2"
            #            }
            #        ],
            #        ...
            #    }
            #
        else:
            responseFromExchange = await self.privatePostMarginTrades(self.extend(request, params))
            #
            #    {
            #        "trades": {
            #            "ADA_USDT": [
            #                {
            #                    "trade_id": "692861757015952517",
            #                    "trade_dt": "1693951853197811824",
            #                    "trade_type": "buy",
            #                    "pair": "ADA_USDT",
            #                    "quantity": "1.96607879",
            #                    "price": "0.2568",
            #                    "amount": "0.50488903"
            #                },
            #            ]
            #            ...
            #        }
            #    }
            #
            response = self.safe_value(responseFromExchange, 'trades')
        result = []
        marketIdsInner = list(response.keys())
        for i in range(0, len(marketIdsInner)):
            marketId = marketIdsInner[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)

    async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#80daa469-ec59-4d0a-b229-6a311d8dd1cd
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#de6f4321-eeac-468c-87f7-c4ad7062e265  # stop market
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#3561b86c-9ff1-436e-8e68-ac926b7eb523  # margin
        :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 [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.stopPrice]: the price at which a trigger order is triggered at
        :param str [params.timeInForce]: *spot only* 'fok', 'ioc' or 'post_only'
        :param boolean [params.postOnly]: *spot only* True for post only orders
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        isMarket = (type == 'market') and (price is None)
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('createOrder', params)
        if marginMode == 'cross':
            raise BadRequest(self.id + ' only supports isolated margin')
        isSpot = (marginMode != 'isolated')
        triggerPrice = self.safe_number_n(params, ['triggerPrice', 'stopPrice', 'stop_price'])
        request: dict = {
            '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
            # '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
        }
        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
        leverage = self.safe_number(params, 'leverage')
        if not isSpot and (leverage is None):
            raise ArgumentsRequired(self.id + ' createOrder requires an extra param params["leverage"] for margin orders')
        params = self.omit(params, ['stopPrice', 'stop_price', 'triggerPrice', 'timeInForce', 'client_id', 'clientOrderId'])
        if price is not None:
            request['price'] = self.price_to_precision(market['symbol'], price)
        response = None
        if isSpot:
            if triggerPrice is not None:
                if type == 'limit':
                    raise BadRequest(self.id + ' createOrder() cannot create stop limit orders for spot, only stop market')
                else:
                    request['type'] = side
                    request['trigger_price'] = self.price_to_precision(symbol, triggerPrice)
                response = await self.privatePostStopMarketOrderCreate(self.extend(request, params))
            else:
                execType = self.safe_string(params, 'exec_type')
                isPostOnly = None
                isPostOnly, params = self.handle_post_only(type == 'market', execType == 'post_only', params)
                timeInForce = self.safe_string(params, 'timeInForce')
                request['price'] = 0 if isMarket else self.price_to_precision(market['symbol'], price)
                if type == 'limit':
                    request['type'] = side
                elif type == 'market':
                    request['type'] = 'market_' + side
                if isPostOnly:
                    request['exec_type'] = 'post_only'
                elif timeInForce is not None:
                    request['exec_type'] = timeInForce
                response = await self.privatePostOrderCreate(self.extend(request, params))
        else:
            if triggerPrice is not None:
                request['stop_price'] = self.price_to_precision(symbol, triggerPrice)
                if type == 'limit':
                    request['type'] = 'stop_limit_' + side
                elif type == 'market':
                    request['type'] = 'stop_' + side
                else:
                    request['type'] = type
            else:
                if type == 'limit' or type == 'market':
                    request['type'] = type + '_' + side
                else:
                    request['type'] = type
            response = await self.privatePostMarginUserOrderCreate(self.extend(request, params))
        return self.parse_order(response, market)

    async def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#1f710d4b-75bc-4b65-ad68-006f863a3f26
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#a4d0aae8-28f7-41ac-94fd-c4030130453d  # stop market
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#705dfec5-2b35-4667-862b-faf54eca6209  # margin
        :param str id: order id
        :param str symbol: not used by exmo cancelOrder()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.trigger]: True to cancel a trigger order
        :param str [params.marginMode]: set to 'cross' or 'isolated' to cancel a margin order
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        request: dict = {}
        stop = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['trigger', 'stop'])
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('cancelOrder', params)
        if marginMode == 'cross':
            raise BadRequest(self.id + ' only supports isolated margin')
        response = None
        if (marginMode == 'isolated'):
            request['order_id'] = id
            response = await self.privatePostMarginUserOrderCancel(self.extend(request, params))
            #
            #    {}
            #
        else:
            if stop:
                request['parent_order_id'] = id
                response = await self.privatePostStopMarketOrderCancel(self.extend(request, params))
                #
                #    {}
                #
            else:
                request['order_id'] = id
                response = await self.privatePostOrderCancel(self.extend(request, params))
                #
                #    {
                #        "error": '',
                #        "result": True
                #    }
                #
        return self.parse_order(response)

    async def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        *spot only* fetches information on an order made by the user
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#cf27781e-28e5-4b39-a52d-3110f5d22459  # spot
        :param str symbol: not used by exmo fetchOrder
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        request: dict = {
            'order_id': str(id),
        }
        response = await 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)
        order['id'] = str(id)
        return order

    async def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all the trades made from a single order
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#cf27781e-28e5-4b39-a52d-3110f5d22459  # spot
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#00810661-9119-46c5-aec5-55abe9cb42c7  # margin
        :param str id: order id
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.marginMode]: set to "isolated" to fetch trades for a margin order
        :returns dict[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=trade-structure>`
        """
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('fetchOrderTrades', params)
        if marginMode == 'cross':
            raise BadRequest(self.id + ' only supports isolated margin')
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request: dict = {
            'order_id': str(id),
        }
        response = None
        if marginMode == 'isolated':
            response = await self.privatePostMarginUserOrderTrades(self.extend(request, params))
            #
            #    {
            #        "trades": [
            #            {
            #                "is_maker": False,
            #                "order_id": "123",
            #                "pair": "BTC_USD",
            #                "price": "54122.25",
            #                "quantity": "0.00069994",
            #                "trade_dt": "1619069561718824428",
            #                "trade_id": "692842802860135010",
            #                "type": "sell"
            #            }
            #        ]
            #    }
            #
        else:
            response = await 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_list(response, 'trades')
        return self.parse_trades(trades, market, since, limit)

    async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#0e135370-daa4-4689-8acd-b6876dee9ba1  # spot open orders
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#a7cfd4f0-476e-4675-b33f-22a46902f245  # margin
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of  open orders structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.marginMode]: set to "isolated" for margin orders
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('fetchOpenOrders', params)
        isMargin = ((marginMode == 'cross') or (marginMode == 'isolated'))
        response = None
        orders = []
        if isMargin:
            response = await self.privatePostMarginUserOrderList(params)
            #
            #    {
            #        "orders": [
            #            {
            #                "client_id": "0",
            #                "comment": "",
            #                "created": "1619068707985325495",
            #                "distance": "0",
            #                "expire": 0,
            #                "funding_currency": "BTC",
            #                "funding_quantity": "0.01",
            #                "funding_rate": "0.02",
            #                "leverage": "2",
            #                "order_id": "123",
            #                "pair": "BTC_USD",
            #                "previous_type": "limit_sell",
            #                "price": "58000",
            #                "quantity": "0.01",
            #                "src": 0,
            #                "stop_price": "0",
            #                "trigger_price": "58000",
            #                "type": "limit_sell",
            #                "updated": 1619068707989411800
            #            }
            #        ]
            #    }
            #
            params = self.extend(params, {
                'status': 'open',
            })
            responseOrders = self.safe_value(response, 'orders')
            orders = self.parse_orders(responseOrders, market, since, limit, params)
        else:
            response = await self.privatePostUserOpenOrders(params)
            #
            #    {
            #        "USDT_USD": [
            #            {
            #                "parent_order_id": "507061384740151010",
            #                "client_id": "100500",
            #                "created": "1589547391",
            #                "type": "stop_market_buy",
            #                "pair": "USDT_USD",
            #                "quantity": "1",
            #                "trigger_price": "5",
            #                "amount": "5"
            #            }
            #        ],
            #        ...
            #    }
            #
            marketIds = list(response.keys())
            for i in range(0, len(marketIds)):
                marketId = marketIds[i]
                marketInner = self.safe_market(marketId)
                params = self.extend(params, {
                    'status': 'open',
                })
                parsedOrders = self.parse_orders(response[marketId], marketInner, since, limit, params)
                orders = self.array_concat(orders, parsedOrders)
        return orders

    def parse_status(self, status):
        if status is None:
            return None
        statuses: dict = {
            'cancel_started': 'canceled',
        }
        if status.find('cancel') >= 0:
            status = 'canceled'
        return self.safe_string(statuses, status, status)

    def parse_side(self, orderType):
        side: dict = {
            'limit_buy': 'buy',
            'limit_sell': 'sell',
            'market_buy': 'buy',
            'market_sell': 'sell',
            'stop_buy': 'buy',
            'stop_sell': 'sell',
            'stop_limit_buy': 'buy',
            'stop_limit_sell': 'sell',
            'trailing_stop_buy': 'buy',
            'trailing_stop_sell': 'sell',
            'stop_market_sell': 'sell',
            'stop_market_buy': 'buy',
            'buy': 'buy',
            'sell': 'sell',
        }
        return self.safe_string(side, orderType, orderType)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # 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
        #             }
        #         ]
        #     }
        #
        # Margin fetchOpenOrders
        #
        #    {
        #        "client_id": "0",
        #        "comment": "",
        #        "created": "1619068707985325495",
        #        "distance": "0",
        #        "expire": 0,
        #        "funding_currency": "BTC",
        #        "funding_quantity": "0.01",
        #        "funding_rate": "0.02",
        #        "leverage": "2",
        #        "order_id": "123",
        #        "pair": "BTC_USD",
        #        "previous_type": "limit_sell",
        #        "price": "58000",
        #        "quantity": "0.01",
        #        "src": 0,
        #        "stop_price": "0",
        #        "trigger_price": "58000",
        #        "type": "limit_sell",
        #        "updated": 1619068707989411800
        #    }
        #
        # Margin fetchClosedOrders
        #
        #    {
        #        "distance": "0",
        #        "event_id": "692842802860022508",
        #        "event_time": "1619069531190173720",
        #        "event_type": "OrderCancelStarted",
        #        "order_id": "123",
        #        "order_status": "cancel_started",
        #        "order_type": "limit_sell",
        #        "pair": "BTC_USD",
        #        "price": "54115",
        #        "quantity": "0.001",
        #        "stop_price": "0",
        #        "trade_id": "0",
        #        "trade_price": "0",
        #        "trade_quantity": "0",
        #        "trade_type": ""
        #    },
        #
        id = self.safe_string_2(order, 'order_id', 'parent_order_id')
        eventTime = self.safe_integer_product_2(order, 'event_time', 'created', 0.000001)
        timestamp = self.safe_timestamp(order, 'created', eventTime)
        orderType = self.safe_string_2(order, 'type', 'order_type')
        side = self.parse_side(orderType)
        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)
        symbol = market['symbol']
        amount = self.safe_string(order, 'quantity')
        if amount is None:
            amountField = 'in_amount' if (side == 'buy') else 'out_amount'
            amount = self.safe_string(order, amountField)
        price = self.safe_string(order, 'price')
        cost = self.safe_string(order, 'amount')
        transactions = self.safe_value(order, 'trades', [])
        clientOrderId = self.safe_integer(order, 'client_id')
        triggerPrice = self.safe_string(order, 'stop_price')
        if triggerPrice == '0':
            triggerPrice = None
        type = None
        if (orderType != 'buy') and (orderType != 'sell'):
            type = orderType
        return self.safe_order({
            'id': id,
            'clientOrderId': clientOrderId,
            'datetime': self.iso8601(timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': self.safe_integer_product(order, 'updated', 0.000001),
            'status': self.parse_status(self.safe_string(order, 'order_status')),
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': triggerPrice,
            'triggerPrice': triggerPrice,
            'cost': cost,
            'amount': amount,
            'filled': None,
            'remaining': None,
            'average': None,
            'trades': transactions,
            'fee': None,
            'info': order,
        }, market)

    async def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetches information on multiple canceled orders made by the user
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#1d2524dd-ae6d-403a-a067-77b50d13fbe5  # margin
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#a51be1d0-af5f-44e4-99d7-f7b04c6067d0  # spot canceled orders
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: timestamp in ms of the earliest order, default is None
        :param int [limit]: max number of orders to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.marginMode]: set to "isolated" for margin orders
        :returns dict: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('fetchOrders', params)
        if marginMode == 'cross':
            raise BadRequest(self.id + ' only supports isolated margin')
        if limit is None:
            limit = 100
        isSpot = (marginMode != 'isolated')
        if symbol is not None:
            marketInner = self.market(symbol)
            symbol = marketInner['symbol']
        request: dict = {
            'limit': limit,
        }
        request['offset'] = limit if (since is not None) else 0
        request['limit'] = limit
        market = None
        if symbol is not None:
            market = self.market(symbol)
        response = None
        if isSpot:
            response = await 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"
            #        }
            #    ]
            #
            params = self.extend(params, {
                'status': 'canceled',
            })
            return self.parse_orders(response, market, since, limit, params)
        else:
            responseSwap = await self.privatePostMarginUserOrderHistory(self.extend(request, params))
            #
            #    {
            #        "items": [
            #            {
            #                "event_id": "692862104574106858",
            #                "event_time": "1694116400173489405",
            #                "event_type": "OrderCancelStarted",
            #                "order_id": "692862104561289319",
            #                "order_type": "stop_limit_sell",
            #                "order_status": "cancel_started",
            #                "trade_id": "0",
            #                "trade_type":"",
            #                "trade_quantity": "0",
            #                "trade_price": "0",
            #                "pair": "ADA_USDT",
            #                "quantity": "12",
            #                "price": "0.23",
            #                "stop_price": "0.22",
            #                "distance": "0"
            #            }
            #            ...
            #        ]
            #    }
            #
            items = self.safe_value(responseSwap, 'items')
            orders = self.parse_orders(items, market, since, limit, params)
            result = []
            for i in range(0, len(orders)):
                order = orders[i]
                if order['status'] == 'canceled':
                    result.append(order)
            return result

    async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
        """
        *margin only* edit a trade order
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#f27ee040-c75f-4b59-b608-d05bd45b7899  # margin
        :param str id: order id
        :param str symbol: unified ccxt_zagrava market symbol
        :param str type: not used by exmo editOrder
        :param str side: not used by exmo editOrder
        :param float [amount]: how much of the currency you want to trade in units of the base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.triggerPrice]: stop price for stop-market and stop-limit orders
        :param str params['marginMode']: must be set to isolated
         *
         * EXCHANGE SPECIFIC PARAMETERS
        :param int [params.distance]: distance for trailing stop orders
        :param int [params.expire]: expiration timestamp in UTC timezone for the order. order will not be expired if expire is 0
        :param str [params.comment]: optional comment for order. up to 50 latin symbols, whitespaces, underscores
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('editOrder', params)
        if marginMode != 'isolated':
            raise BadRequest(self.id + ' editOrder() can only be used for isolated margin orders')
        triggerPrice = self.safe_number_n(params, ['triggerPrice', 'stopPrice', 'stop_price'])
        params = self.omit(params, ['triggerPrice', 'stopPrice'])
        request: dict = {
            'order_id': id,  # id of the open order
        }
        if amount is not None:
            request['quantity'] = amount
        if price is not None:
            request['price'] = self.price_to_precision(market['symbol'], price)
        if triggerPrice is not None:
            request['stop_price'] = self.price_to_precision(market['symbol'], triggerPrice)
        response = await self.privatePostMarginUserOrderUpdate(self.extend(request, params))
        return self.parse_order(response)

    async def fetch_deposit_address(self, code: str, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#c8f9ced9-7ab6-4383-a6a4-bc54469ba60e
        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt_zagrava.com/#/?id=address-structure>`
        """
        await self.load_markets()
        response = await 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

    async def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        make a withdrawal
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#3ab9c34d-ad58-4f87-9c57-2e2ea88a8325
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        await self.load_markets()
        currency = self.currency(code)
        request: dict = {
            '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 = await self.privatePostWithdrawCrypt(self.extend(request, params))
        return self.parse_transaction(response, currency)

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

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # fetchDepositsWithdrawals
        #
        #    {
        #        "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": ""
        #    }
        #
        # withdraw
        #
        #    {
        #        "result": True,
        #        "error": "",
        #        "task_id": 11775077
        #    }
        #
        timestamp = self.safe_timestamp_2(transaction, 'dt', 'created')
        amountString = self.safe_string(transaction, 'amount')
        if amountString is not None:
            amountString = Precise.string_abs(amountString)
        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
        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 = {
            'currency': None,
            'cost': None,
            'rate': 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:
                transactionFees = self.safe_value(self.options, 'transactionFees', {})
                codeFees = self.safe_value(transactionFees, code, {})
                feeCost = self.safe_string(codeFees, key)
            # 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':
                    amountString = Precise.string_sub(amountString, feeCost)
                fee['cost'] = self.parse_number(feeCost)
                fee['currency'] = code
        return {
            'info': transaction,
            'id': self.safe_string_2(transaction, 'order_id', 'task_id'),
            'txid': txid,
            'type': type,
            'currency': code,
            'network': self.safe_string(transaction, 'provider'),
            'amount': self.parse_number(amountString),
            'status': self.parse_transaction_status(self.safe_string_lower(transaction, 'status')),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'address': address,
            'addressFrom': None,
            'addressTo': address,
            'tag': None,
            'tagFrom': None,
            'tagTo': None,
            'updated': self.safe_timestamp(transaction, 'updated'),
            'comment': comment,
            'internal': None,
            'fee': fee,
        }

    async def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch history of deposits and withdrawals
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#31e69a33-4849-4e6a-b4b4-6d574238f6a7
        :param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
        :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
        :param int [limit]: max number of deposit/withdrawals to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a list of `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        request: dict = {}
        if since is not None:
            request['date'] = self.parse_to_int(since / 1000)
        currency = None
        if code is not None:
            currency = self.currency(code)
        response = await 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)

    async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#97f1becd-7aad-4e0e-babe-7bbe09e33706
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch withdrawals for
        :param int [limit]: the maximum number of withdrawals structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        currency = None
        request: dict = {
            '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 = await 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_list(response, 'items', [])
        return self.parse_transactions(items, currency, since, limit)

    async def fetch_withdrawal(self, id: str, code: Str = None, params={}):
        """
        fetch data on a currency withdrawal via the withdrawal id
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#97f1becd-7aad-4e0e-babe-7bbe09e33706
        :param str id: withdrawal id
        :param str code: unified currency code of the currency withdrawn, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        currency = None
        request: dict = {
            'order_id': id,
            'type': 'withdraw',
        }
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        response = await 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_dict(items, 0, {})
        return self.parse_transaction(first, currency)

    async def fetch_deposit(self, id=None, code: Str = None, params={}):
        """
        fetch information on a deposit
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#97f1becd-7aad-4e0e-babe-7bbe09e33706
        :param str id: deposit id
        :param str code: unified currency code, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        currency = None
        request: dict = {
            'order_id': id,
            'type': 'deposit',
        }
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        response = await 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_dict(items, 0, {})
        return self.parse_transaction(first, currency)

    async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all deposits made to an account
        :see: https://documenter.getpostman.com/view/10287440/SzYXWKPi#97f1becd-7aad-4e0e-babe-7bbe09e33706
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch deposits for
        :param int [limit]: the maximum number of deposits structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        currency = None
        request: dict = {
            '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 = await 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_list(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: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if response is None:
            return None  # fallback to default error handler
        if ('error' in response) and not ('result' in response):
            # error: {
            #     "code": "140434",
            #     "msg": "Your margin balance is not sufficient to place the order for '5 TON'. Please top up your margin wallet by "2.5 USDT"."
            # }
            #
            errorCode = self.safe_value(response, 'error', {})
            messageError = self.safe_string(errorCode, 'msg')
            code = self.safe_string(errorCode, 'code')
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], messageError, feedback)
            raise ExchangeError(feedback)
        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_bool(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)
        return None
