# -*- 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.yobit import ImplicitAPI
import hashlib
from ccxt_zagrava.base.types import Balances, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFees
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import ArgumentsRequired
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 DDoSProtection
from ccxt_zagrava.base.errors import RateLimitExceeded
from ccxt_zagrava.base.errors import ExchangeNotAvailable
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 yobit(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(yobit, self).describe(), {
            'id': 'yobit',
            'name': 'YoBit',
            'countries': ['RU'],
            'rateLimit': 2000,  # responses are cached every 2 seconds
            'version': '3',
            'pro': False,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': False,
                'cancelOrder': True,
                'closeAllPositions': False,
                'closePosition': False,
                'createDepositAddress': True,
                'createMarketOrder': False,
                'createOrder': True,
                'createReduceOnlyOrder': False,
                'createStopLimitOrder': False,
                'createStopMarketOrder': False,
                'createStopOrder': False,
                'fetchBalance': True,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchDepositAddress': True,
                'fetchDeposits': False,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchLeverage': False,
                'fetchLeverageTiers': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderBooks': True,
                'fetchPosition': False,
                'fetchPositionHistory': False,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPositionsForSymbol': False,
                'fetchPositionsHistory': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': True,
                'fetchTransactions': False,
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawals': False,
                'reduceMargin': False,
                'setLeverage': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'transfer': False,
                'withdraw': True,
                'ws': False,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766910-cdcbfdae-5eea-11e7-9859-03fea873272d.jpg',
                'api': {
                    'public': 'https://yobit.net/api',
                    'private': 'https://yobit.net/tapi',
                },
                'www': 'https://www.yobit.net',
                'doc': 'https://www.yobit.net/en/api/',
                'fees': 'https://www.yobit.net/en/fees/',
            },
            'api': {
                'public': {
                    'get': {
                        'depth/{pair}': 1,
                        'info': 1,
                        'ticker/{pair}': 1,
                        'trades/{pair}': 1,
                    },
                },
                'private': {
                    'post': {
                        'ActiveOrders': 1,
                        'CancelOrder': 1,
                        'GetDepositAddress': 1,
                        'getInfo': 1,
                        'OrderInfo': 1,
                        'Trade': 1,
                        'TradeHistory': 1,
                        'WithdrawCoinsToAddress': 1,
                    },
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.002,
                    'taker': 0.002,
                },
                'funding': {
                    'withdraw': {},
                },
            },
            'commonCurrencies': {
                'AIR': 'AirCoin',
                'ANI': 'ANICoin',
                'ANT': 'AntsCoin',  # what is self, a coin for ants?
                'ATMCHA': 'ATM',
                'ASN': 'Ascension',
                'AST': 'Astral',
                'ATM': 'Autumncoin',
                'AUR': 'AuroraCoin',
                'BAB': 'Babel',
                'BAN': 'BANcoin',
                'BCC': 'BCH',
                'BCS': 'BitcoinStake',
                'BITS': 'Bitstar',
                'BLN': 'Bulleon',
                'BNS': 'Benefit Bonus Coin',
                'BOT': 'BOTcoin',
                'BON': 'BONES',
                'BPC': 'BitcoinPremium',
                'BST': 'BitStone',
                'BTS': 'Bitshares2',
                'CAT': 'BitClave',
                'CBC': 'CryptoBossCoin',
                'CMT': 'CometCoin',
                'COIN': 'Coin.com',
                'COV': 'Coven Coin',
                'COVX': 'COV',
                'CPC': 'Capricoin',
                'CREDIT': 'Creditbit',
                'CS': 'CryptoSpots',
                'DCT': 'Discount',
                'DFT': 'DraftCoin',
                'DGD': 'DarkGoldCoin',
                'DIRT': 'DIRTY',
                'DROP': 'FaucetCoin',
                'DSH': 'DASH',
                'EGC': 'EverGreenCoin',
                'EGG': 'EggCoin',
                'EKO': 'EkoCoin',
                'ENTER': 'ENTRC',
                'EPC': 'ExperienceCoin',
                'ESC': 'EdwardSnowden',
                'EUROPE': 'EUROP',
                'EXT': 'LifeExtension',
                'FUND': 'FUNDChains',
                'FUNK': 'FUNKCoin',
                'FX': 'FCoin',
                'GCC': 'GlobalCryptocurrency',
                'GEN': 'Genstake',
                'GENE': 'Genesiscoin',
                'GMR': 'Gimmer',
                'GOLD': 'GoldMint',
                'GOT': 'Giotto Coin',
                'GSX': 'GlowShares',
                'GT': 'GTcoin',
                'HTML5': 'HTML',
                'HYPERX': 'HYPER',
                'ICN': 'iCoin',
                'INSANE': 'INSN',
                'JNT': 'JointCoin',
                'JPC': 'JupiterCoin',
                'JWL': 'Jewels',
                'KNC': 'KingN Coin',
                'LBTCX': 'LiteBitcoin',
                'LIZI': 'LiZi',
                'LOC': 'LocoCoin',
                'LOCX': 'LOC',
                'LUNYR': 'LUN',
                'LUN': 'LunarCoin',  # they just change the ticker if it is already taken
                'LUNA': 'Luna Coin',
                'MASK': 'Yobit MASK',
                'MDT': 'Midnight',
                'MEME': 'Memez Token',  # conflict with Meme Inu / Degenerator Meme
                'MIS': 'MIScoin',
                'MM': 'MasterMint',  # conflict with MilliMeter
                'NAV': 'NavajoCoin',
                'NBT': 'NiceBytes',
                'OMG': 'OMGame',
                'ONX': 'Onix',
                'PAC': '$PAC',
                'PLAY': 'PlayCoin',
                'PIVX': 'Darknet',
                'PURE': 'PurePOS',
                'PUTIN': 'PutinCoin',
                'SPACE': 'Spacecoin',
                'STK': 'StakeCoin',
                'SUB': 'Subscriptio',
                'PAY': 'EPAY',
                'PLC': 'Platin Coin',
                'RAI': 'RaiderCoin',
                'RCN': 'RCoin',
                'REP': 'Republicoin',
                'RUR': 'RUB',
                'SBTC': 'Super Bitcoin',
                'SMC': 'SmartCoin',
                'SOLO': 'SoloCoin',
                'SOUL': 'SoulCoin',
                'STAR': 'StarCoin',
                'SUPER': 'SuperCoin',
                'TNS': 'Transcodium',
                'TTC': 'TittieCoin',
                'UNI': 'Universe',
                'UST': 'Uservice',
                'VOL': 'VolumeCoin',
                'XIN': 'XINCoin',
                'XMT': 'SummitCoin',
                'XRA': 'Ratecoin',
            },
            'options': {
                'maxUrlLength': 2048,
                'fetchOrdersRequiresSymbol': True,
                'networks': {
                    'ETH': 'ERC20',
                    'TRX': 'TRC20',
                    'BSC': 'BEP20',
                },
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    '803': InvalidOrder,  # "Count could not be less than 0.001."(selling below minAmount)
                    '804': InvalidOrder,  # "Count could not be more than 10000."(buying above maxAmount)
                    '805': InvalidOrder,  # "price could not be less than X."(minPrice violation on buy & sell)
                    '806': InvalidOrder,  # "price could not be more than X."(maxPrice violation on buy & sell)
                    '807': InvalidOrder,  # "cost could not be less than X."(minCost violation on buy & sell)
                    '831': InsufficientFunds,  # "Not enougth X to create buy order."(buying with balance.quote < order.cost)
                    '832': InsufficientFunds,  # "Not enougth X to create sell order."(selling with balance.base < order.amount)
                    '833': OrderNotFound,  # "Order with id X was not found."(cancelling non-existent, closed and cancelled order)
                },
                'broad': {
                    'Invalid pair name': ExchangeError,  # {"success":0,"error":"Invalid pair name: btc_eth"}
                    'invalid api key': AuthenticationError,
                    'invalid sign': AuthenticationError,
                    'api key dont have trade permission': AuthenticationError,
                    'invalid parameter': InvalidOrder,
                    'invalid order': InvalidOrder,
                    'The given order has already been cancelled': InvalidOrder,
                    'Requests too often': DDoSProtection,
                    'not available': ExchangeNotAvailable,
                    'data unavailable': ExchangeNotAvailable,
                    'external service unavailable': ExchangeNotAvailable,
                    'Total transaction amount': InvalidOrder,  # {"success": 0, "error": "Total transaction amount is less than minimal total: 0.00010000"}
                    'The given order has already been closed and cannot be cancelled': InvalidOrder,
                    'Insufficient funds': InsufficientFunds,
                    'invalid key': AuthenticationError,
                    'invalid nonce': InvalidNonce,  # {"success":0,"error":"invalid nonce(has already been used)"}'
                    'Total order amount is less than minimal amount': InvalidOrder,
                    'Rate Limited': RateLimitExceeded,
                },
            },
            'orders': {},  # orders cache / emulation
        })

    def parse_balance(self, response) -> Balances:
        balances = self.safe_dict(response, 'return', {})
        timestamp = self.safe_integer(balances, 'server_time')
        result: dict = {
            'info': response,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
        }
        free = self.safe_dict(balances, 'funds', {})
        total = self.safe_dict(balances, 'funds_incl_orders', {})
        currencyIds = list(self.extend(free, total).keys())
        for i in range(0, len(currencyIds)):
            currencyId = currencyIds[i]
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(free, currencyId)
            account['total'] = self.safe_string(total, currencyId)
            result[code] = account
        return self.safe_balance(result)

    async def fetch_balance(self, params={}) -> Balances:
        """
        :see: https://yobit.net/en/api
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        response = await self.privatePostGetInfo(params)
        #
        #     {
        #         "success":1,
        #         "return":{
        #             "funds":{
        #                 "ltc":22,
        #                 "nvc":423.998,
        #                 "ppc":10,
        #             },
        #             "funds_incl_orders":{
        #                 "ltc":32,
        #                 "nvc":523.998,
        #                 "ppc":20,
        #             },
        #             "rights":{
        #                 "info":1,
        #                 "trade":0,
        #                 "withdraw":0
        #             },
        #             "transaction_count":0,
        #             "open_orders":1,
        #             "server_time":1418654530
        #         }
        #     }
        #
        return self.parse_balance(response)

    async def fetch_markets(self, params={}) -> List[Market]:
        """
        :see: https://yobit.net/en/api
        retrieves data on all markets for yobit
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = await self.publicGetInfo(params)
        #
        #     {
        #         "server_time":1615856752,
        #         "pairs":{
        #             "ltc_btc":{
        #                 "decimal_places":8,
        #                 "min_price":0.00000001,
        #                 "max_price":10000,
        #                 "min_amount":0.0001,
        #                 "min_total":0.0001,
        #                 "hidden":0,
        #                 "fee":0.2,
        #                 "fee_buyer":0.2,
        #                 "fee_seller":0.2
        #             },
        #         },
        #     }
        #
        markets = self.safe_dict(response, 'pairs', {})
        keys = list(markets.keys())
        result = []
        for i in range(0, len(keys)):
            id = keys[i]
            market = markets[id]
            baseId, quoteId = id.split('_')
            base = baseId.upper()
            quote = quoteId.upper()
            base = self.safe_currency_code(base)
            quote = self.safe_currency_code(quote)
            hidden = self.safe_integer(market, 'hidden')
            feeString = self.safe_string(market, 'fee')
            feeString = Precise.string_div(feeString, '100')
            # yobit maker = taker
            result.append({
                'id': id,
                'symbol': base + '/' + quote,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'active': (hidden == 0),
                'contract': False,
                'linear': None,
                'inverse': None,
                'taker': self.parse_number(feeString),
                'maker': self.parse_number(feeString),
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'decimal_places'))),
                    'price': self.parse_number(self.parse_precision(self.safe_string(market, 'decimal_places'))),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': self.safe_number(market, 'min_amount'),
                        'max': self.safe_number(market, 'max_amount'),
                    },
                    'price': {
                        'min': self.safe_number(market, 'min_price'),
                        'max': self.safe_number(market, 'max_price'),
                    },
                    'cost': {
                        'min': self.safe_number(market, 'min_total'),
                        'max': None,
                    },
                },
                'created': None,
                'info': market,
            })
        return result

    async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        :see: https://yobit.net/en/api
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [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  # default = 150, max = 2000
        response = await self.publicGetDepthPair(self.extend(request, params))
        market_id_in_reponse = (market['id'] in response)
        if not market_id_in_reponse:
            raise ExchangeError(self.id + ' ' + market['symbol'] + ' order book is empty or not available')
        orderbook = response[market['id']]
        return self.parse_order_book(orderbook, symbol)

    async def fetch_order_books(self, symbols: Strings = None, limit: Int = None, params={}):
        """
        :see: https://yobit.net/en/api
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data for multiple markets
        :param str[]|None symbols: list of unified market symbols, all symbols fetched if None, default is None
        :param int [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,
            # 'ignore_invalid': True,
        }
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetDepthPair(self.extend(request, params))
        result: dict = {}
        ids = list(response.keys())
        for i in range(0, len(ids)):
            id = ids[i]
            symbol = self.safe_symbol(id)
            result[symbol] = self.parse_order_book(response[id], symbol)
        return result

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #     {
        #         "high": 0.03497582,
        #         "low": 0.03248474,
        #         "avg": 0.03373028,
        #         "vol": 120.11485715062999,
        #         "vol_cur": 3572.24914074,
        #         "last": 0.0337611,
        #         "buy": 0.0337442,
        #         "sell": 0.03377798,
        #         "updated": 1537522009
        #     }
        #
        timestamp = self.safe_timestamp(ticker, 'updated')
        last = self.safe_string(ticker, 'last')
        return self.safe_ticker({
            'symbol': self.safe_symbol(None, market),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': self.safe_string(ticker, 'buy'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'sell'),
            '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_cur'),
            'quoteVolume': self.safe_string(ticker, 'vol'),
            'info': ticker,
        }, market)

    async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        :see: https://yobit.net/en/api
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        if symbols is None:
            raise ArgumentsRequired(self.id + ' fetchTickers() requires "symbols" argument')
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        ids = None
        if symbols is None:
            ids = self.ids
        else:
            ids = self.market_ids(symbols)
        idsLength = len(ids)
        idsString = '-'.join(ids)
        maxLength = self.safe_integer(self.options, 'maxUrlLength', 2048)
        # max URL length is 2048 symbols, including http schema, hostname, tld, etc...
        lenghtOfBaseUrl = 30  # the url including api-base and endpoint dir is 30 chars
        actualLength = len(idsString) + lenghtOfBaseUrl
        if actualLength > maxLength:
            raise ArgumentsRequired(self.id + ' fetchTickers() is being requested for ' + str(idsLength) + ' markets(which has an URL length of ' + str(actualLength) + ' characters), but it exceedes max URL length(' + str(maxLength) + '), please pass limisted symbols array to fetchTickers to fit in one request')
        request: dict = {
            'pair': idsString,
        }
        tickers = await self.publicGetTickerPair(self.extend(request, params))
        result: dict = {}
        keys = list(tickers.keys())
        for k in range(0, len(keys)):
            id = keys[k]
            ticker = tickers[id]
            market = self.safe_market(id)
            symbol = market['symbol']
            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:
        """
        :see: https://yobit.net/en/api
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        tickers = await self.fetch_tickers([symbol], params)
        return tickers[symbol]

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # fetchTrades(public)
        #
        #      {
        #          "type":"bid",
        #          "price":0.14046179,
        #          "amount":0.001,
        #          "tid":200256901,
        #          "timestamp":1649861004
        #      }
        #
        # fetchMyTrades(private)
        #
        #      {
        #          "pair":"doge_usdt",
        #          "type":"sell",
        #          "amount":139,
        #          "rate":0.139,
        #          "order_id":"2101103631773172",
        #          "is_your_order":1,
        #          "timestamp":"1649861561"
        #      }
        #
        timestamp = self.safe_timestamp(trade, 'timestamp')
        side = self.safe_string(trade, 'type')
        if side == 'ask':
            side = 'sell'
        elif side == 'bid':
            side = 'buy'
        priceString = self.safe_string_2(trade, 'rate', 'price')
        id = self.safe_string_2(trade, 'trade_id', 'tid')
        order = self.safe_string(trade, 'order_id')
        marketId = self.safe_string(trade, 'pair')
        symbol = self.safe_symbol(marketId, market)
        amountString = self.safe_string(trade, 'amount')
        # arguments for calculateFee(need to be numbers)
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        type = 'limit'  # all trades are still limit trades
        fee = None
        feeCostString = self.safe_number(trade, 'commission')
        if feeCostString is not None:
            feeCurrencyId = self.safe_string(trade, 'commissionCurrency')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCostString,
                'currency': feeCurrencyCode,
            }
        isYourOrder = self.safe_string(trade, 'is_your_order')
        if isYourOrder is not None:
            if fee is None:
                feeInNumbers = self.calculate_fee(symbol, type, side, amount, price, 'taker')
                fee = {
                    'currency': self.safe_string(feeInNumbers, 'currency'),
                    'cost': self.safe_string(feeInNumbers, 'cost'),
                    'rate': self.safe_string(feeInNumbers, 'rate'),
                }
        return self.safe_trade({
            'id': id,
            'order': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': type,
            'side': side,
            'takerOrMaker': None,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': fee,
            'info': trade,
        }, market)

    async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        :see: https://yobit.net/en/api
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [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'],
        }
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetTradesPair(self.extend(request, params))
        #
        #      {
        #          "doge_usdt": [
        #              {
        #                  "type":"ask",
        #                  "price":0.13956743,
        #                  "amount":0.0008,
        #                  "tid":200256900,
        #                  "timestamp":1649860521
        #              },
        #          ]
        #      }
        #
        if isinstance(response, list):
            numElements = len(response)
            if numElements == 0:
                return []
        result = self.safe_list(response, market['id'], [])
        return self.parse_trades(result, market, since, limit)

    async def fetch_trading_fees(self, params={}) -> TradingFees:
        """
        :see: https://yobit.net/en/api
        fetch the trading fees for multiple markets
        :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
        """
        await self.load_markets()
        response = await self.publicGetInfo(params)
        #
        #     {
        #         "server_time":1615856752,
        #         "pairs":{
        #             "ltc_btc":{
        #                 "decimal_places":8,
        #                 "min_price":0.00000001,
        #                 "max_price":10000,
        #                 "min_amount":0.0001,
        #                 "min_total":0.0001,
        #                 "hidden":0,
        #                 "fee":0.2,
        #                 "fee_buyer":0.2,
        #                 "fee_seller":0.2
        #             },
        #             ...
        #         },
        #     }
        #
        pairs = self.safe_dict(response, 'pairs', {})
        marketIds = list(pairs.keys())
        result: dict = {}
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            pair = self.safe_dict(pairs, marketId, {})
            symbol = self.safe_symbol(marketId, None, '_')
            takerString = self.safe_string(pair, 'fee_buyer')
            makerString = self.safe_string(pair, 'fee_seller')
            taker = self.parse_number(Precise.string_div(takerString, '100'))
            maker = self.parse_number(Precise.string_div(makerString, '100'))
            result[symbol] = {
                'info': pair,
                'symbol': symbol,
                'taker': taker,
                'maker': maker,
                'percentage': True,
                'tierBased': False,
            }
        return result

    async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        :see: https://yobit.net/en/api
        create a trade order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: must be '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
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if type == 'market':
            raise ExchangeError(self.id + ' createOrder() allows limit orders only')
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
            'type': side,
            'amount': self.amount_to_precision(symbol, amount),
            'rate': self.price_to_precision(symbol, price),
        }
        response = await self.privatePostTrade(self.extend(request, params))
        #
        #      {
        #          "success":1,
        #          "return": {
        #              "received":0,
        #              "remains":10,
        #              "order_id":1101103635125179,
        #              "funds": {
        #                  "usdt":27.84756553,
        #                  "usdttrc20":0,
        #                  "doge":19.98327206
        #              },
        #              "funds_incl_orders": {
        #                  "usdt":30.35256553,
        #                  "usdttrc20":0,
        #                  "doge":19.98327206
        #               },
        #               "server_time":1650114256
        #           }
        #       }
        #
        result = self.safe_dict(response, 'return')
        return self.parse_order(result, market)

    async def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        :see: https://yobit.net/en/api
        cancels an open order
        :param str id: order id
        :param str symbol: not used by yobit cancelOrder()
        :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': int(id),
        }
        response = await self.privatePostCancelOrder(self.extend(request, params))
        #
        #      {
        #          "success":1,
        #          "return": {
        #              "order_id":1101103632552304,
        #              "funds": {
        #                  "usdt":30.71055443,
        #                  "usdttrc20":0,
        #                  "doge":9.98327206
        #              },
        #              "funds_incl_orders": {
        #                  "usdt":31.81275443,
        #                  "usdttrc20":0,
        #                  "doge":9.98327206
        #              },
        #              "server_time":1649918298
        #          }
        #      }
        #
        result = self.safe_dict(response, 'return', {})
        return self.parse_order(result)

    def parse_order_status(self, status: Str):
        statuses: dict = {
            '0': 'open',
            '1': 'closed',
            '2': 'canceled',
            '3': 'open',  # or partially-filled and canceled? https://github.com/ccxt_zagrava/ccxt_zagrava/issues/1594
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # createOrder(private)
        #
        #      {
        #          "received":0,
        #          "remains":10,
        #          "order_id":1101103635125179,
        #          "funds": {
        #              "usdt":27.84756553,
        #              "usdttrc20":0,
        #              "doge":19.98327206
        #          },
        #          "funds_incl_orders": {
        #              "usdt":30.35256553,
        #              "usdttrc20":0,
        #              "doge":19.98327206
        #          },
        #          "server_time":1650114256
        #      }
        #
        # fetchOrder(private)
        #
        #      {
        #          "id: "1101103635103335",  # id-field is manually added in fetchOrder() from exchange response id-order dictionary structure
        #          "pair":"doge_usdt",
        #          "type":"buy",
        #          "start_amount":10,
        #          "amount":10,
        #          "rate":0.05,
        #          "timestamp_created":"1650112553",
        #          "status":0
        #      }
        #
        # fetchOpenOrders(private)
        #
        #      {
        #          "id":"1101103635103335",  # id-field is manually added in fetchOpenOrders() from exchange response id-order dictionary structure
        #          "pair":"doge_usdt",
        #          "type":"buy",
        #          "amount":10,
        #          "rate":0.05,
        #          "timestamp_created":"1650112553",
        #          "status":0
        #      }
        #
        # cancelOrder(private)
        #
        #      {
        #          "order_id":1101103634000197,
        #          "funds": {
        #              "usdt":31.81275443,
        #              "usdttrc20":0,
        #              "doge":9.98327206
        #          },
        #          "funds_incl_orders": {
        #              "usdt":31.81275443,
        #              "usdttrc20":0,
        #              "doge":9.98327206
        #          }
        #      }
        #
        id = self.safe_string_2(order, 'id', 'order_id')
        status = self.parse_order_status(self.safe_string(order, 'status', 'open'))
        if id == '0':
            id = self.safe_string(order, 'init_order_id')
            status = 'closed'
        timestamp = self.safe_timestamp_2(order, 'timestamp_created', 'server_time')
        marketId = self.safe_string(order, 'pair')
        symbol = self.safe_symbol(marketId, market)
        amount = self.safe_string(order, 'start_amount')
        remaining = self.safe_string_2(order, 'amount', 'remains')
        filled = self.safe_string(order, 'received', '0.0')
        price = self.safe_string(order, 'rate')
        fee = None
        type = 'limit'
        side = self.safe_string(order, 'type')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': None,
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'triggerPrice': None,
            'cost': None,
            'amount': amount,
            'remaining': remaining,
            'filled': filled,
            'status': status,
            'fee': fee,
            'average': None,
            'trades': None,
        }, market)

    async def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        :see: https://yobit.net/en/api
        fetches information on an order made by the user
        :param str symbol: not used by yobit 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': int(id),
        }
        response = await self.privatePostOrderInfo(self.extend(request, params))
        id = str(id)
        orders = self.safe_dict(response, 'return', {})
        #
        #      {
        #          "success":1,
        #          "return": {
        #              "1101103635103335": {
        #                  "pair":"doge_usdt",
        #                  "type":"buy",
        #                  "start_amount":10,
        #                  "amount":10,
        #                  "rate":0.05,
        #                  "timestamp_created":"1650112553",
        #                  "status":0
        #              }
        #          }
        #      }
        #
        return self.parse_order(self.extend({'id': id}, orders[id]))

    async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        :see: https://yobit.net/en/api
        fetch all unfilled currently open orders
        :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 order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument')
        await self.load_markets()
        request: dict = {}
        market = None
        if symbol is not None:
            marketInner = self.market(symbol)
            request['pair'] = marketInner['id']
        response = await self.privatePostActiveOrders(self.extend(request, params))
        #
        #      {
        #          "success":1,
        #          "return": {
        #              "1101103634006799": {
        #                  "pair":"doge_usdt",
        #                  "type":"buy",
        #                  "amount":10,
        #                  "rate":0.1,
        #                  "timestamp_created":"1650034937",
        #                  "status":0
        #              },
        #              "1101103634006738": {
        #                  "pair":"doge_usdt",
        #                  "type":"buy",
        #                  "amount":10,
        #                  "rate":0.1,
        #                  "timestamp_created":"1650034932",
        #                  "status":0
        #              }
        #          }
        #      }
        #
        result = self.safe_dict(response, 'return', {})
        return self.parse_orders(result, market, since, limit)

    async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        :see: https://yobit.net/en/api
        fetch all trades made by the user
        :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 structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :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')
        await self.load_markets()
        market = self.market(symbol)
        # some derived classes use camelcase notation for request fields
        request: dict = {
            # 'from': 123456789,  # trade ID, from which the display starts numerical 0(test result: liqui ignores self field)
            # 'count': 1000,  # the number of trades for display numerical, default = 1000
            # 'from_id': trade ID, from which the display starts numerical 0
            # 'end_id': trade ID on which the display ends numerical ∞
            # 'order': 'ASC',  # sorting, default = DESC(test result: liqui ignores self field, most recent trade always goes last)
            # 'since': 1234567890,  # UTC start time, default = 0(test result: liqui ignores self field)
            # 'end': 1234567890,  # UTC end time, default = ∞(test result: liqui ignores self field)
            'pair': market['id'],
        }
        if limit is not None:
            request['count'] = limit
        if since is not None:
            request['since'] = self.parse_to_int(since / 1000)
        response = await self.privatePostTradeHistory(self.extend(request, params))
        #
        #      {
        #          "success":1,
        #          "return": {
        #              "200257004": {
        #                  "pair":"doge_usdt",
        #                  "type":"sell",
        #                  "amount":139,
        #                  "rate":0.139,
        #                  "order_id":"2101103631773172",
        #                  "is_your_order":1,
        #                  "timestamp":"1649861561"
        #              }
        #          }
        #      }
        #
        trades = self.safe_dict(response, 'return', {})
        ids = list(trades.keys())
        result = []
        for i in range(0, len(ids)):
            id = ids[i]
            trade = self.parse_trade(self.extend(trades[id], {
                'trade_id': id,
            }), market)
            result.append(trade)
        return self.filter_by_symbol_since_limit(result, market['symbol'], since, limit)

    async def create_deposit_address(self, code: str, params={}):
        """
        :see: https://yobit.net/en/api
        create a currency deposit address
        :param str code: unified currency code of the currency for the deposit address
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt_zagrava.com/#/?id=address-structure>`
        """
        request: dict = {
            'need_new': 1,
        }
        response = await self.fetch_deposit_address(code, self.extend(request, params))
        address = self.safe_string(response, 'address')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': None,
            'info': response['info'],
        }

    async def fetch_deposit_address(self, code: str, params={}):
        """
        :see: https://yobit.net/en/api
        fetch the deposit address for a currency associated with self account
        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt_zagrava.com/#/?id=address-structure>`
        """
        await self.load_markets()
        currency = self.currency(code)
        currencyId = currency['id']
        networks = self.safe_dict(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:
            if network != 'ERC20':
                currencyId = currencyId + network.lower()
            params = self.omit(params, 'network')
        request: dict = {
            'coinName': currencyId,
            'need_new': 0,
        }
        response = await self.privatePostGetDepositAddress(self.extend(request, params))
        address = self.safe_string(response['return'], 'address')
        self.check_address(address)
        return {
            'id': None,
            'currency': code,
            'address': address,
            'tag': None,
            'network': None,
            'info': response,
            'txid': None,
            'type': None,
            'amount': None,
            'status': None,
            'timestamp': None,
            'datetime': None,
            'addressFrom': None,
            'addressTo': None,
            'tagFrom': None,
            'tagTo': None,
            'updated': None,
            'comment': None,
            'fee': {
                'currency': None,
                'cost': None,
                'rate': None,
            },
        }

    async def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        :see: https://yobit.net/en/api
        make a withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str 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)
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'coinName': currency['id'],
            'amount': amount,
            'address': address,
        }
        # no docs on the tag, yet...
        if tag is not None:
            raise ExchangeError(self.id + ' withdraw() does not support the tag argument yet due to a lack of docs on withdrawing with tag/memo on behalf of the exchange.')
        response = await self.privatePostWithdrawCoinsToAddress(self.extend(request, params))
        return {
            'info': response,
            'id': None,
            'txid': None,
            'type': None,
            'currency': None,
            'network': None,
            'amount': None,
            'status': None,
            'timestamp': None,
            'datetime': None,
            'address': None,
            'addressFrom': None,
            'addressTo': None,
            'tag': None,
            'tagFrom': None,
            'tagTo': None,
            'updated': None,
            'comment': None,
            'fee': {
                'currency': None,
                'cost': None,
                'rate': None,
            },
        }

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

    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 'success' in response:
            #
            # 1 - Liqui only returns the integer 'success' key from their private API
            #
            #     {"success": 1, ...} httpCode == 200
            #     {"success": 0, ...} httpCode == 200
            #
            # 2 - However, exchanges derived from Liqui, can return non-integers
            #
            #     It can be a numeric string
            #     {"sucesss": "1", ...}
            #     {"sucesss": "0", ...}, httpCode >= 200(can be 403, 502, etc)
            #
            #     Or just a string
            #     {"success": "true", ...}
            #     {"success": "false", ...}, httpCode >= 200
            #
            #     Or a boolean
            #     {"success": True, ...}
            #     {"success": False, ...}, httpCode >= 200
            #
            # 3 - Oversimplified, Python PEP8 forbids comparison operator(==) of different types
            #
            # 4 - We do not want to copy-paste and duplicate the code of self handler to other exchanges derived from Liqui
            #
            # To cover points 1, 2, 3 and 4 combined self handler should work like self:
            #
            success = self.safe_value(response, 'success')  # don't replace with safeBool here
            if isinstance(success, str):
                if (success == 'true') or (success == '1'):
                    success = True
                else:
                    success = False
            if not success:
                code = self.safe_string(response, 'code')
                message = self.safe_string(response, 'error')
                feedback = self.id + ' ' + body
                self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
                self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
                self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
                raise ExchangeError(feedback)  # unknown message
        return None
