# -*- 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.base.exchange import Exchange
from ccxt_zagrava.abstract.latoken import ImplicitAPI
import hashlib
from ccxt_zagrava.base.types import Balances, Currencies, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, Transaction, TransferEntry, TransferEntries
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 AccountSuspended
from ccxt_zagrava.base.errors import ArgumentsRequired
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import BadSymbol
from ccxt_zagrava.base.errors import InsufficientFunds
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import NotSupported
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


class latoken(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(latoken, self).describe(), {
            'id': 'latoken',
            'name': 'Latoken',
            'countries': ['KY'],  # Cayman Islands
            'version': 'v2',
            'rateLimit': 1000,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'closeAllPositions': False,
                'closePosition': False,
                'createOrder': True,
                'createPostOnlyOrder': False,
                'createStopLimitOrder': True,
                'createStopMarketOrder': False,
                'createStopOrder': True,
                'fetchBalance': True,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': True,
                'fetchDepositAddress': False,
                'fetchDepositAddresses': False,
                'fetchDepositAddressesByNetwork': False,
                'fetchDepositsWithdrawals': True,
                'fetchDepositWithdrawFees': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchPosition': False,
                'fetchPositionHistory': False,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPositionsForSymbol': False,
                'fetchPositionsHistory': False,
                'fetchPositionsRisk': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': False,
                'fetchTransactions': 'emulated',
                'fetchTransfer': False,
                'fetchTransfers': True,
                'transfer': True,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/61511972-24c39f00-aa01-11e9-9f7c-471f1d6e5214.jpg',
                'api': {
                    'rest': 'https://api.latoken.com',
                },
                'www': 'https://latoken.com',
                'doc': [
                    'https://api.latoken.com',
                ],
                'fees': 'https://latoken.com/fees',
                'referral': 'https://latoken.com/invite?r=mvgp2djk',
            },
            'api': {
                'public': {
                    'get': {
                        'book/{currency}/{quote}': 1,
                        'chart/week': 1,
                        'chart/week/{currency}/{quote}': 1,
                        'currency': 1,
                        'currency/available': 1,
                        'currency/quotes': 1,
                        'currency/{currency}': 1,
                        'pair': 1,
                        'pair/available': 1,
                        'ticker': 1,
                        'ticker/{base}/{quote}': 1,
                        'time': 1,
                        'trade/history/{currency}/{quote}': 1,
                        'trade/fee/{currency}/{quote}': 1,
                        'trade/feeLevels': 1,
                        'transaction/bindings': 1,
                    },
                },
                'private': {
                    'get': {
                        'auth/account': 1,
                        'auth/account/currency/{currency}/{type}': 1,
                        'auth/order': 1,
                        'auth/order/getOrder/{id}': 1,
                        'auth/order/pair/{currency}/{quote}': 1,
                        'auth/order/pair/{currency}/{quote}/active': 1,
                        'auth/stopOrder': 1,
                        'auth/stopOrder/getOrder/{id}': 1,
                        'auth/stopOrder/pair/{currency}/{quote}': 1,
                        'auth/stopOrder/pair/{currency}/{quote}/active': 1,
                        'auth/trade': 1,
                        'auth/trade/pair/{currency}/{quote}': 1,
                        'auth/trade/fee/{currency}/{quote}': 1,
                        'auth/transaction': 1,
                        'auth/transaction/bindings': 1,
                        'auth/transaction/bindings/{currency}': 1,
                        'auth/transaction/{id}': 1,
                        'auth/transfer': 1,
                    },
                    'post': {
                        'auth/order/cancel': 1,
                        'auth/order/cancelAll': 1,
                        'auth/order/cancelAll/{currency}/{quote}': 1,
                        'auth/order/place': 1,
                        'auth/spot/deposit': 1,
                        'auth/spot/withdraw': 1,
                        'auth/stopOrder/cancel': 1,
                        'auth/stopOrder/cancelAll': 1,
                        'auth/stopOrder/cancelAll/{currency}/{quote}': 1,
                        'auth/stopOrder/place': 1,
                        'auth/transaction/depositAddress': 1,
                        'auth/transaction/withdraw': 1,
                        'auth/transaction/withdraw/cancel': 1,
                        'auth/transaction/withdraw/confirm': 1,
                        'auth/transaction/withdraw/resendCode': 1,
                        'auth/transfer/email': 1,
                        'auth/transfer/id': 1,
                        'auth/transfer/phone': 1,
                    },
                },
            },
            'precisionMode': TICK_SIZE,
            'fees': {
                'trading': {
                    'feeSide': 'get',
                    'tierBased': False,
                    'percentage': True,
                    'maker': self.parse_number('0.0049'),
                    'taker': self.parse_number('0.0049'),
                },
            },
            'commonCurrencies': {
                'BUX': 'Buxcoin',
                'CBT': 'Community Business Token',
                'CTC': 'CyberTronchain',
                'DMD': 'Diamond Coin',
                'FREN': 'Frenchie',
                'GDX': 'GoldenX',
                'GEC': 'Geco One',
                'GEM': 'NFTmall',
                'GMT': 'GMT Token',
                'IMC': 'IMCoin',
                'MT': 'Monarch',
                'TPAY': 'Tetra Pay',
                'TRADE': 'Smart Trade Coin',
                'TSL': 'Treasure SL',
                'UNO': 'Unobtanium',
                'WAR': 'Warrior Token',
            },
            'exceptions': {
                'exact': {
                    'INTERNAL_ERROR': ExchangeError,  # internal server error. You can contact our support to solve self problem. {"message":"Internal Server Error","error":"INTERNAL_ERROR","status":"FAILURE"}
                    'SERVICE_UNAVAILABLE': ExchangeNotAvailable,  # requested information currently not available. You can contact our support to solve self problem or retry later.
                    'NOT_AUTHORIZED': AuthenticationError,  # user's query not authorized. Check if you are logged in.
                    'FORBIDDEN': PermissionDenied,  # you don't have enough access rights.
                    'BAD_REQUEST': BadRequest,  # some bad request, for example bad fields values or something else. Read response message for more information.
                    'NOT_FOUND': ExchangeError,  # entity not found. Read message for more information.
                    'ACCESS_DENIED': PermissionDenied,  # access is denied. Probably you don't have enough access rights, you contact our support.
                    'REQUEST_REJECTED': ExchangeError,  # user's request rejected for some reasons. Check error message.
                    'HTTP_MEDIA_TYPE_NOT_SUPPORTED': BadRequest,  # http media type not supported.
                    'MEDIA_TYPE_NOT_ACCEPTABLE': BadRequest,  # media type not acceptable
                    'METHOD_ARGUMENT_NOT_VALID': BadRequest,  # one of method argument is invalid. Check argument types and error message for more information.
                    'VALIDATION_ERROR': BadRequest,  # check errors field to get reasons.
                    'ACCOUNT_EXPIRED': AccountSuspended,  # restore your account or create a new one.
                    'BAD_CREDENTIALS': AuthenticationError,  # invalid username or password.
                    'COOKIE_THEFT': AuthenticationError,  # cookie has been stolen. Let's try reset your cookies.
                    'CREDENTIALS_EXPIRED': AccountSuspended,  # credentials expired.
                    'INSUFFICIENT_AUTHENTICATION': AuthenticationError,  # for example, 2FA required.
                    'UNKNOWN_LOCATION': AuthenticationError,  # user logged from unusual location, email confirmation required.
                    'TOO_MANY_REQUESTS': RateLimitExceeded,  # too many requests at the time. A response header X-Rate-Limit-Remaining indicates the number of allowed request per a period.
                    'INSUFFICIENT_FUNDS': InsufficientFunds,  # {"message":"not enough balance on the spot account for currency(USDT), need(20.000)","error":"INSUFFICIENT_FUNDS","status":"FAILURE"}
                    'ORDER_VALIDATION': InvalidOrder,  # {"message":"Quantity(0) is not positive","error":"ORDER_VALIDATION","status":"FAILURE"}
                    'BAD_TICKS': InvalidOrder,  # {"status":"FAILURE","message":"Quantity(1.4) does not match quantity tick(10)","error":"BAD_TICKS","errors":null,"result":false}
                },
                'broad': {
                    'invalid API key, signature or digest': AuthenticationError,  # {"result":false,"message":"invalid API key, signature or digest","error":"BAD_REQUEST","status":"FAILURE"}
                    'The API key was revoked': AuthenticationError,  # {"result":false,"message":"The API key was revoked","error":"BAD_REQUEST","status":"FAILURE"}
                    'request expired or bad': InvalidNonce,  # {"result":false,"message":"request expired or bad <timeAlive>/<timestamp> format","error":"BAD_REQUEST","status":"FAILURE"}
                    'For input string': BadRequest,  # {"result":false,"message":"Internal error","error":"For input string: \"NaN\"","status":"FAILURE"}
                    'Unable to resolve currency by tag': BadSymbol,  # {"message":"Unable to resolve currency by tag(None)","error":"NOT_FOUND","status":"FAILURE"}
                    "Can't find currency with tag": BadSymbol,  # {"status":"FAILURE","message":"Can't find currency with tag = None","error":"NOT_FOUND","errors":null,"result":false}
                    'Unable to place order because pair is in inactive state': BadSymbol,  # {"message":"Unable to place order because pair is in inactive state(PAIR_STATUS_INACTIVE)","error":"ORDER_VALIDATION","status":"FAILURE"}
                    'API keys are not available for': AccountSuspended,  # {"result":false,"message":"API keys are not available for FROZEN user","error":"BAD_REQUEST","status":"FAILURE"}
                },
            },
            'options': {
                'defaultType': 'spot',
                'types': {
                    'wallet': 'ACCOUNT_TYPE_WALLET',
                    'funding': 'ACCOUNT_TYPE_WALLET',
                    'spot': 'ACCOUNT_TYPE_SPOT',
                },
                'accounts': {
                    'ACCOUNT_TYPE_WALLET': 'wallet',
                    'ACCOUNT_TYPE_SPOT': 'spot',
                },
                'fetchTradingFee': {
                    'method': 'fetchPrivateTradingFee',  # or 'fetchPublicTradingFee'
                },
            },
        })

    def nonce(self):
        return self.milliseconds() - self.options['timeDifference']

    def fetch_time(self, params={}):
        """
        fetches the current integer timestamp in milliseconds from the exchange server
        :see: https://api.latoken.com/doc/v2/#tag/Time/operation/currentTime
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = self.publicGetTime(params)
        #
        #     {
        #         "serverTime": 1570615577321
        #     }
        #
        return self.safe_integer(response, 'serverTime')

    def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for latoken
        :see: https://api.latoken.com/doc/v2/#tag/Pair/operation/getActivePairs
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        currencies = self.fetch_currencies_from_cache(params)
        #
        #     [
        #         {
        #             "id":"1a075819-9e0b-48fc-8784-4dab1d186d6d",
        #             "status":"CURRENCY_STATUS_ACTIVE",
        #             "type":"CURRENCY_TYPE_ALTERNATIVE",  # CURRENCY_TYPE_CRYPTO, CURRENCY_TYPE_IEO
        #             "name":"MyCryptoBank",
        #             "tag":"MCB",
        #             "description":"",
        #             "logo":"",
        #             "decimals":18,
        #             "created":1572912000000,
        #             "tier":1,
        #             "assetClass":"ASSET_CLASS_UNKNOWN",
        #             "minTransferAmount":0
        #         },
        #         {
        #             "id":"db02758e-2507-46a5-a805-7bc60355b3eb",
        #             "status":"CURRENCY_STATUS_ACTIVE",
        #             "type":"CURRENCY_TYPE_FUTURES_CONTRACT",
        #             "name":"BTC USDT Futures Contract",
        #             "tag":"BTCUSDT",
        #             "description":"",
        #             "logo":"",
        #             "decimals":8,
        #             "created":1589459984395,
        #             "tier":1,
        #             "assetClass":"ASSET_CLASS_UNKNOWN",
        #             "minTransferAmount":0
        #         },
        #     ]
        #
        response = self.publicGetPair(params)
        #
        #     [
        #         {
        #             "id":"dba4289b-6b46-4d94-bf55-49eec9a163ad",
        #             "status":"PAIR_STATUS_ACTIVE",  # CURRENCY_STATUS_INACTIVE
        #             "baseCurrency":"fb9b53d6-bbf6-472f-b6ba-73cc0d606c9b",
        #             "quoteCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #             "priceTick":"0.000000100000000000",
        #             "priceDecimals":7,
        #             "quantityTick":"0.010000000",
        #             "quantityDecimals":2,
        #             "costDisplayDecimals":7,
        #             "created":1572957210501,
        #             "minOrderQuantity":"0",
        #             "maxOrderCostUsd":"999999999999999999",
        #             "minOrderCostUsd":"0",
        #             "externalSymbol":""
        #         }
        #     ]
        #
        if self.safe_value(self.options, 'adjustForTimeDifference', True):
            self.load_time_difference()
        currenciesById = self.index_by(currencies, 'id')
        result = []
        for i in range(0, len(response)):
            market = response[i]
            id = self.safe_string(market, 'id')
            # the exchange shows them inverted
            baseId = self.safe_string(market, 'baseCurrency')
            quoteId = self.safe_string(market, 'quoteCurrency')
            baseCurrency = self.safe_value(currenciesById, baseId)
            quoteCurrency = self.safe_value(currenciesById, quoteId)
            if baseCurrency is not None and quoteCurrency is not None:
                base = self.safe_currency_code(self.safe_string(baseCurrency, 'tag'))
                quote = self.safe_currency_code(self.safe_string(quoteCurrency, 'tag'))
                lowercaseQuote = quote.lower()
                capitalizedQuote = self.capitalize(lowercaseQuote)
                status = self.safe_string(market, 'status')
                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': (status == 'PAIR_STATUS_ACTIVE'),  # assuming True
                    'contract': False,
                    'linear': None,
                    'inverse': None,
                    'contractSize': None,
                    'expiry': None,
                    'expiryDatetime': None,
                    'strike': None,
                    'optionType': None,
                    'precision': {
                        'amount': self.safe_number(market, 'quantityTick'),
                        'price': self.safe_number(market, 'priceTick'),
                    },
                    'limits': {
                        'leverage': {
                            'min': None,
                            'max': None,
                        },
                        'amount': {
                            'min': self.safe_number(market, 'minOrderQuantity'),
                            'max': None,
                        },
                        'price': {
                            'min': None,
                            'max': None,
                        },
                        'cost': {
                            'min': self.safe_number(market, 'minOrderCost' + capitalizedQuote),
                            'max': self.safe_number(market, 'maxOrderCost' + capitalizedQuote),
                        },
                    },
                    'created': self.safe_integer(market, 'created'),
                    'info': market,
                })
        return result

    def fetch_currencies_from_cache(self, params={}):
        # self method is now redundant
        # currencies are now fetched before markets
        options = self.safe_value(self.options, 'fetchCurrencies', {})
        timestamp = self.safe_integer(options, 'timestamp')
        expires = self.safe_integer(options, 'expires', 1000)
        now = self.milliseconds()
        if (timestamp is None) or ((now - timestamp) > expires):
            response = self.publicGetCurrency(params)
            self.options['fetchCurrencies'] = self.extend(options, {
                'response': response,
                'timestamp': now,
            })
        return self.safe_value(self.options['fetchCurrencies'], 'response')

    def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = self.fetch_currencies_from_cache(params)
        #
        #     [
        #         {
        #             "id":"1a075819-9e0b-48fc-8784-4dab1d186d6d",
        #             "status":"CURRENCY_STATUS_ACTIVE",
        #             "type":"CURRENCY_TYPE_ALTERNATIVE",  # CURRENCY_TYPE_CRYPTO, CURRENCY_TYPE_IEO
        #             "name":"MyCryptoBank",
        #             "tag":"MCB",
        #             "description":"",
        #             "logo":"",
        #             "decimals":18,
        #             "created":1572912000000,
        #             "tier":1,
        #             "assetClass":"ASSET_CLASS_UNKNOWN",
        #             "minTransferAmount":0
        #         },
        #         {
        #             "id":"db02758e-2507-46a5-a805-7bc60355b3eb",
        #             "status":"CURRENCY_STATUS_ACTIVE",
        #             "type":"CURRENCY_TYPE_FUTURES_CONTRACT",
        #             "name":"BTC USDT Futures Contract",
        #             "tag":"BTCUSDT",
        #             "description":"",
        #             "logo":"",
        #             "decimals":8,
        #             "created":1589459984395,
        #             "tier":1,
        #             "assetClass":"ASSET_CLASS_UNKNOWN",
        #             "minTransferAmount":0
        #         },
        #     ]
        #
        result: dict = {}
        for i in range(0, len(response)):
            currency = response[i]
            id = self.safe_string(currency, 'id')
            tag = self.safe_string(currency, 'tag')
            code = self.safe_currency_code(tag)
            fee = self.safe_number(currency, 'fee')
            currencyType = self.safe_string(currency, 'type')
            type = None
            if currencyType == 'CURRENCY_TYPE_ALTERNATIVE':
                type = 'other'
            else:
                # CURRENCY_TYPE_CRYPTO and CURRENCY_TYPE_IEO are all cryptos
                type = 'crypto'
            status = self.safe_string(currency, 'status')
            active = (status == 'CURRENCY_STATUS_ACTIVE')
            name = self.safe_string(currency, 'name')
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': name,
                'type': type,
                'active': active,
                'deposit': None,
                'withdraw': None,
                'fee': fee,
                'precision': self.parse_number(self.parse_precision(self.safe_string(currency, 'decimals'))),
                'limits': {
                    'amount': {
                        'min': self.safe_number(currency, 'minTransferAmount'),
                        'max': None,
                    },
                    'withdraw': {
                        'min': None,
                        'max': None,
                    },
                },
                'networks': {},
            }
        return result

    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://api.latoken.com/doc/v2/#tag/Account/operation/getBalancesByUser
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        self.load_markets()
        response = self.privateGetAuthAccount(params)
        #
        #     [
        #         {
        #             "id": "e5852e02-8711-431c-9749-a6f5503c6dbe",
        #             "status": "ACCOUNT_STATUS_ACTIVE",
        #             "type": "ACCOUNT_TYPE_WALLET",
        #             "timestamp": "1635920106506",
        #             "currency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #             "available": "100.000000",
        #             "blocked": "0.000000"
        #         },
        #         {
        #             "id": "369df204-acbc-467e-a25e-b16e3cc09cf6",
        #             "status": "ACCOUNT_STATUS_ACTIVE",
        #             "type": "ACCOUNT_TYPE_SPOT",
        #             "timestamp": "1635920106504",
        #             "currency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #             "available": "100.000000",
        #             "blocked": "0.000000"
        #         }
        #     ]
        #
        result: dict = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        maxTimestamp = None
        defaultType = self.safe_string_2(self.options, 'fetchBalance', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        types = self.safe_value(self.options, 'types', {})
        accountType = self.safe_string(types, type, type)
        balancesByType = self.group_by(response, 'type')
        balances = self.safe_value(balancesByType, accountType, [])
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string(balance, 'currency')
            timestamp = self.safe_integer(balance, 'timestamp')
            if timestamp is not None:
                if maxTimestamp is None:
                    maxTimestamp = timestamp
                else:
                    maxTimestamp = max(maxTimestamp, timestamp)
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(balance, 'available')
            account['used'] = self.safe_string(balance, 'blocked')
            result[code] = account
        result['timestamp'] = maxTimestamp
        result['datetime'] = self.iso8601(maxTimestamp)
        return self.safe_balance(result)

    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://api.latoken.com/doc/v2/#tag/Order-Book/operation/getOrderBook
        :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
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'currency': market['baseId'],
            'quote': market['quoteId'],
        }
        if limit is not None:
            request['limit'] = limit  # max 1000
        response = self.publicGetBookCurrencyQuote(self.extend(request, params))
        #
        #     {
        #         "ask":[
        #             {"price":"4428.76","quantity":"0.08136","cost":"360.3239136","accumulated":"360.3239136"},
        #             {"price":"4429.77","quantity":"1.11786","cost":"4951.8626922","accumulated":"5312.1866058"},
        #             {"price":"4430.94","quantity":"1.78418","cost":"7905.5945292","accumulated":"13217.781135"},
        #         ],
        #         "bid":[
        #             {"price":"4428.43","quantity":"0.13675","cost":"605.5878025","accumulated":"605.5878025"},
        #             {"price":"4428.19","quantity":"0.03619","cost":"160.2561961","accumulated":"765.8439986"},
        #             {"price":"4428.15","quantity":"0.02926","cost":"129.567669","accumulated":"895.4116676"},
        #         ],
        #         "totalAsk":"53.14814",
        #         "totalBid":"112216.9029791"
        #     }
        #
        return self.parse_order_book(response, symbol, None, 'bid', 'ask', 'price', 'quantity')

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #    {
        #        "symbol": "92151d82-df98-4d88-9a4d-284fa9eca49f/0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #        "baseCurrency": "92151d82-df98-4d88-9a4d-284fa9eca49f",
        #        "quoteCurrency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #        "volume24h": "165723597.189022176000000000",
        #        "volume7d": "934505768.625109571000000000",
        #        "change24h": "0.0200",
        #        "change7d": "-6.4200",
        #        "amount24h": "6438.457663100000000000",
        #        "amount7d": "35657.785013800000000000",
        #        "lastPrice": "25779.16",
        #        "lastQuantity": "0.248403300000000000",
        #        "bestBid": "25778.74",
        #        "bestBidQuantity": "0.6520232",
        #        "bestAsk": "25779.17",
        #        "bestAskQuantity": "0.4956043",
        #        "updateTimestamp": "1693965231406"
        #    }
        #
        marketId = self.safe_string(ticker, 'symbol')
        last = self.safe_string(ticker, 'lastPrice')
        timestamp = self.safe_integer_omit_zero(ticker, 'updateTimestamp')  # sometimes latoken provided '0' ts from /ticker endpoint
        return self.safe_ticker({
            'symbol': self.safe_symbol(marketId, market),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'low': None,
            'high': None,
            'bid': self.safe_string(ticker, 'bestBid'),
            'bidVolume': self.safe_string(ticker, 'bestBidQuantity'),
            'ask': self.safe_string(ticker, 'bestAsk'),
            'askVolume': self.safe_string(ticker, 'bestAskQuantity'),
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': self.safe_string(ticker, 'change24h'),
            'average': None,
            'baseVolume': self.safe_string(ticker, 'amount24h'),
            'quoteVolume': self.safe_string(ticker, 'volume24h'),
            'info': ticker,
        }, market)

    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://api.latoken.com/doc/v2/#tag/Ticker/operation/getTicker
        :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>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'base': market['baseId'],
            'quote': market['quoteId'],
        }
        response = self.publicGetTickerBaseQuote(self.extend(request, params))
        #
        #    {
        #        "symbol": "92151d82-df98-4d88-9a4d-284fa9eca49f/0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #        "baseCurrency": "92151d82-df98-4d88-9a4d-284fa9eca49f",
        #        "quoteCurrency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #        "volume24h": "165723597.189022176000000000",
        #        "volume7d": "934505768.625109571000000000",
        #        "change24h": "0.0200",
        #        "change7d": "-6.4200",
        #        "amount24h": "6438.457663100000000000",
        #        "amount7d": "35657.785013800000000000",
        #        "lastPrice": "25779.16",
        #        "lastQuantity": "0.248403300000000000",
        #        "bestBid": "25778.74",
        #        "bestBidQuantity": "0.6520232",
        #        "bestAsk": "25779.17",
        #        "bestAskQuantity": "0.4956043",
        #        "updateTimestamp": "1693965231406"
        #    }
        #
        return self.parse_ticker(response, market)

    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://api.latoken.com/doc/v2/#tag/Ticker/operation/getAllTickers
        :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>`
        """
        self.load_markets()
        response = self.publicGetTicker(params)
        #
        #    [
        #        {
        #            "symbol": "92151d82-df98-4d88-9a4d-284fa9eca49f/0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #            "baseCurrency": "92151d82-df98-4d88-9a4d-284fa9eca49f",
        #            "quoteCurrency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #            "volume24h": "165723597.189022176000000000",
        #            "volume7d": "934505768.625109571000000000",
        #            "change24h": "0.0200",
        #            "change7d": "-6.4200",
        #            "amount24h": "6438.457663100000000000",
        #            "amount7d": "35657.785013800000000000",
        #            "lastPrice": "25779.16",
        #            "lastQuantity": "0.248403300000000000",
        #            "bestBid": "25778.74",
        #            "bestBidQuantity": "0.6520232",
        #            "bestAsk": "25779.17",
        #            "bestAskQuantity": "0.4956043",
        #            "updateTimestamp": "1693965231406"
        #        }
        #    ]
        #
        return self.parse_tickers(response, symbols)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # fetchTrades(public)
        #
        #     {
        #         "id":"c152f814-8eeb-44f0-8f3f-e5c568f2ffcf",
        #         "isMakerBuyer":false,
        #         "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #         "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "price":"4435.56",
        #         "quantity":"0.32534",
        #         "cost":"1443.0650904",
        #         "timestamp":1635854642725,
        #         "makerBuyer":false
        #     }
        #
        # fetchMyTrades(private)
        #
        #     {
        #         "id":"02e02533-b4bf-4ba9-9271-24e2108dfbf7",
        #         "isMakerBuyer":false,
        #         "direction":"TRADE_DIRECTION_BUY",
        #         "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #         "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "price":"4564.32",
        #         "quantity":"0.01000",
        #         "cost":"45.6432",
        #         "fee":"0.223651680000000000",
        #         "order":"c9cac6a0-484c-4892-88e7-ad51b39f2ce1",
        #         "timestamp":1635921580399,
        #         "makerBuyer":false
        #     }
        #
        type = None
        timestamp = self.safe_integer(trade, 'timestamp')
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'quantity')
        costString = self.safe_string(trade, 'cost')
        makerBuyer = self.safe_value(trade, 'makerBuyer')
        side = self.safe_string(trade, 'direction')
        if side is None:
            side = 'sell' if makerBuyer else 'buy'
        else:
            if side == 'TRADE_DIRECTION_BUY':
                side = 'buy'
            elif side == 'TRADE_DIRECTION_SELL':
                side = 'sell'
        isBuy = (side == 'buy')
        takerOrMaker = 'maker' if (makerBuyer and isBuy) else 'taker'
        baseId = self.safe_string(trade, 'baseCurrency')
        quoteId = self.safe_string(trade, 'quoteCurrency')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = base + '/' + quote
        if symbol in self.markets:
            market = self.market(symbol)
        id = self.safe_string(trade, 'id')
        orderId = self.safe_string(trade, 'order')
        feeCost = self.safe_string(trade, 'fee')
        fee = None
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': quote,
            }
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': type,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': priceString,
            'amount': amountString,
            'cost': costString,
            'fee': fee,
        }, market)

    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://api.latoken.com/doc/v2/#tag/Trade/operation/getTradesByPair
        :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>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'currency': market['baseId'],
            'quote': market['quoteId'],
            # 'from': str(since),  # milliseconds
            # 'limit': limit,  # default 100, limit 100
        }
        if limit is not None:
            request['limit'] = min(limit, 100)  # default 100, limit 100
        response = self.publicGetTradeHistoryCurrencyQuote(self.extend(request, params))
        #
        #     [
        #         {"id":"c152f814-8eeb-44f0-8f3f-e5c568f2ffcf","isMakerBuyer":false,"baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f","quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5","price":"4435.56","quantity":"0.32534","cost":"1443.0650904","timestamp":1635854642725,"makerBuyer":false},
        #         {"id":"cfecbefb-3d11-43d7-b9d4-fa16211aad8a","isMakerBuyer":false,"baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f","quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5","price":"4435.13","quantity":"0.26540","cost":"1177.083502","timestamp":1635854641114,"makerBuyer":false},
        #         {"id":"f43d3ec8-db94-49f3-b534-91dbc2779296","isMakerBuyer":true,"baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f","quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5","price":"4435.00","quantity":"0.41738","cost":"1851.0803","timestamp":1635854640323,"makerBuyer":true},
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
        """
        fetch the trading fees for a market
        :see: https://api.latoken.com/doc/v2/#tag/Trade/operation/getFeeByPair
        :see: https://api.latoken.com/doc/v2/#tag/Trade/operation/getAuthFeeByPair
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `fee structure <https://docs.ccxt_zagrava.com/#/?id=fee-structure>`
        """
        options = self.safe_value(self.options, 'fetchTradingFee', {})
        defaultMethod = self.safe_string(options, 'method', 'fetchPrivateTradingFee')
        method = self.safe_string(params, 'method', defaultMethod)
        params = self.omit(params, 'method')
        if method == 'fetchPrivateTradingFee':
            return self.fetch_private_trading_fee(symbol, params)
        elif method == 'fetchPublicTradingFee':
            return self.fetch_public_trading_fee(symbol, params)
        else:
            raise NotSupported(self.id + ' not support self method')

    def fetch_public_trading_fee(self, symbol: str, params={}):
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'currency': market['baseId'],
            'quote': market['quoteId'],
        }
        response = self.publicGetTradeFeeCurrencyQuote(self.extend(request, params))
        #
        #     {
        #         "makerFee": "0.004900000000000000",
        #         "takerFee": "0.004900000000000000",
        #         "type": "FEE_SCHEME_TYPE_PERCENT_QUOTE",
        #         "take": "FEE_SCHEME_TAKE_PROPORTION"
        #     }
        #
        return {
            'info': response,
            'symbol': market['symbol'],
            'maker': self.safe_number(response, 'makerFee'),
            'taker': self.safe_number(response, 'takerFee'),
            'percentage': None,
            'tierBased': None,
        }

    def fetch_private_trading_fee(self, symbol: str, params={}):
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'currency': market['baseId'],
            'quote': market['quoteId'],
        }
        response = self.privateGetAuthTradeFeeCurrencyQuote(self.extend(request, params))
        #
        #     {
        #         "makerFee": "0.004900000000000000",
        #         "takerFee": "0.004900000000000000",
        #         "type": "FEE_SCHEME_TYPE_PERCENT_QUOTE",
        #         "take": "FEE_SCHEME_TAKE_PROPORTION"
        #     }
        #
        return {
            'info': response,
            'symbol': market['symbol'],
            'maker': self.safe_number(response, 'makerFee'),
            'taker': self.safe_number(response, 'takerFee'),
            'percentage': None,
            'tierBased': None,
        }

    def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user
        :see: https://api.latoken.com/doc/v2/#tag/Trade/operation/getTradesByTrader
        :see: https://api.latoken.com/doc/v2/#tag/Trade/operation/getTradesByAssetAndTrader
        :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>`
        """
        self.load_markets()
        request: dict = {
            # 'currency': market['baseId'],
            # 'quote': market['quoteId'],
            # 'from': self.milliseconds(),
            # 'limit': limit,  # default '100'
        }
        market = None
        if limit is not None:
            request['limit'] = limit  # default 100
        response = None
        if symbol is not None:
            market = self.market(symbol)
            request['currency'] = market['baseId']
            request['quote'] = market['quoteId']
            response = self.privateGetAuthTradePairCurrencyQuote(self.extend(request, params))
        else:
            response = self.privateGetAuthTrade(self.extend(request, params))
        #
        #     [
        #         {
        #             "id":"02e02533-b4bf-4ba9-9271-24e2108dfbf7",
        #             "isMakerBuyer":false,
        #             "direction":"TRADE_DIRECTION_BUY",
        #             "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #             "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #             "price":"4564.32",
        #             "quantity":"0.01000",
        #             "cost":"45.6432",
        #             "fee":"0.223651680000000000",
        #             "order":"c9cac6a0-484c-4892-88e7-ad51b39f2ce1",
        #             "timestamp":1635921580399,
        #             "makerBuyer":false
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_order_status(self, status: Str):
        statuses: dict = {
            'ORDER_STATUS_PLACED': 'open',
            'ORDER_STATUS_CLOSED': 'closed',
            'ORDER_STATUS_CANCELLED': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_order_type(self, status):
        statuses: dict = {
            'ORDER_TYPE_MARKET': 'market',
            'ORDER_TYPE_LIMIT': 'limit',
        }
        return self.safe_string(statuses, status, status)

    def parse_time_in_force(self, timeInForce: Str):
        timeInForces: dict = {
            'ORDER_CONDITION_GOOD_TILL_CANCELLED': 'GTC',
            'ORDER_CONDITION_IMMEDIATE_OR_CANCEL': 'IOC',
            'ORDER_CONDITION_FILL_OR_KILL': 'FOK',
        }
        return self.safe_string(timeInForces, timeInForce, timeInForce)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # createOrder
        #
        #    {
        #        "baseCurrency": "f7dac554-8139-4ff6-841f-0e586a5984a0",
        #        "quoteCurrency": "a5a7a7a9-e2a3-43f9-8754-29a02f6b709b",
        #        "side": "BID",
        #        "clientOrderId": "my-wonderful-order-number-71566",
        #        "price": "10103.19",
        #        "stopPrice": "10103.19",
        #        "quantity": "3.21",
        #        "timestamp": 1568185507
        #    }
        #
        # fetchOrder, fetchOpenOrders, fetchOrders
        #
        #     {
        #         "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
        #         "status":"ORDER_STATUS_PLACED",
        #         "side":"ORDER_SIDE_BUY",
        #         "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
        #         "type":"ORDER_TYPE_LIMIT",
        #         "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #         "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
        #         "price":"4000.00",
        #         "quantity":"0.01",
        #         "cost":"40.000000000000000000",
        #         "filled":"0",
        #         "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
        #         "creator":"ORDER_CREATOR_USER",
        #         "creatorId":"",
        #         "timestamp":1635920767648
        #     }
        #
        # cancelOrder
        #
        #     {
        #         "message":"cancellation request successfully submitted",
        #         "status":"SUCCESS",
        #         "id":"a631426d-3543-45ba-941e-75f7825afb0f"
        #     }
        #
        id = self.safe_string(order, 'id')
        timestamp = self.safe_integer(order, 'timestamp')
        baseId = self.safe_string(order, 'baseCurrency')
        quoteId = self.safe_string(order, 'quoteCurrency')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = None
        if (base is not None) and (quote is not None):
            symbol = base + '/' + quote
            if symbol in self.markets:
                market = self.market(symbol)
        orderSide = self.safe_string(order, 'side')
        side = None
        if orderSide is not None:
            parts = orderSide.split('_')
            partsLength = len(parts)
            side = self.safe_string_lower(parts, partsLength - 1)
        type = self.parse_order_type(self.safe_string(order, 'type'))
        price = self.safe_string(order, 'price')
        amount = self.safe_string(order, 'quantity')
        filled = self.safe_string(order, 'filled')
        cost = self.safe_string(order, 'cost')
        status = self.parse_order_status(self.safe_string(order, 'status'))
        message = self.safe_string(order, 'message')
        if message is not None:
            if message.find('cancel') >= 0:
                status = 'canceled'
            elif message.find('accept') >= 0:
                status = 'open'
        clientOrderId = self.safe_string(order, 'clientOrderId')
        timeInForce = self.parse_time_in_force(self.safe_string(order, 'condition'))
        triggerPrice = self.safe_string(order, 'stopPrice')
        return self.safe_order({
            'id': id,
            'clientOrderId': clientOrderId,
            'info': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': triggerPrice,
            'triggerPrice': triggerPrice,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'average': None,
            'remaining': None,
            'fee': None,
            'trades': None,
        }, market)

    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://api.latoken.com/doc/v2/#tag/Order/operation/getMyActiveOrdersByPair
        :see: https://api.latoken.com/doc/v2/#tag/StopOrder/operation/getMyActiveStopOrdersByPair  # stop
        :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 boolean [params.trigger]: True if fetching trigger orders
        :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')
        self.load_markets()
        response = None
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, 'stop')
        # privateGetAuthOrderActive doesn't work even though its listed at https://api.latoken.com/doc/v2/#tag/Order/operation/getMyActiveOrders
        market = self.market(symbol)
        request: dict = {
            'currency': market['baseId'],
            'quote': market['quoteId'],
        }
        if isTrigger:
            response = self.privateGetAuthStopOrderPairCurrencyQuoteActive(self.extend(request, params))
        else:
            response = self.privateGetAuthOrderPairCurrencyQuoteActive(self.extend(request, params))
        #
        #     [
        #         {
        #             "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
        #             "status":"ORDER_STATUS_PLACED",
        #             "side":"ORDER_SIDE_BUY",
        #             "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
        #             "type":"ORDER_TYPE_LIMIT",
        #             "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #             "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #             "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
        #             "price":"4000.00",
        #             "quantity":"0.01000",
        #             "cost":"40.00",
        #             "filled":"0.00000",
        #             "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
        #             "creator":"USER",
        #             "creatorId":"",
        #             "timestamp":1635920767648
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple orders made by the user
        :see: https://api.latoken.com/doc/v2/#tag/Order/operation/getMyOrders
        :see: https://api.latoken.com/doc/v2/#tag/Order/operation/getMyOrdersByPair
        :see: https://api.latoken.com/doc/v2/#tag/StopOrder/operation/getMyStopOrders       # stop
        :see: https://api.latoken.com/doc/v2/#tag/StopOrder/operation/getMyStopOrdersByPair  # stop
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.trigger]: True if fetching trigger orders
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {
            # 'currency': market['baseId'],
            # 'quote': market['quoteId'],
            # 'from': self.milliseconds(),
            # 'limit': limit,  # default '100'
        }
        market = None
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['stop', 'trigger'])
        if limit is not None:
            request['limit'] = limit  # default 100
        response = None
        if symbol is not None:
            market = self.market(symbol)
            request['currency'] = market['baseId']
            request['quote'] = market['quoteId']
            if isTrigger:
                response = self.privateGetAuthStopOrderPairCurrencyQuote(self.extend(request, params))
            else:
                response = self.privateGetAuthOrderPairCurrencyQuote(self.extend(request, params))
        else:
            if isTrigger:
                response = self.privateGetAuthStopOrder(self.extend(request, params))
            else:
                response = self.privateGetAuthOrder(self.extend(request, params))
        #
        #     [
        #         {
        #             "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
        #             "status":"ORDER_STATUS_PLACED",
        #             "side":"ORDER_SIDE_BUY",
        #             "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
        #             "type":"ORDER_TYPE_LIMIT",
        #             "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #             "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #             "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
        #             "price":"4000.00",
        #             "quantity":"0.01000",
        #             "cost":"40.00",
        #             "filled":"0.00000",
        #             "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
        #             "creator":"USER",
        #             "creatorId":"",
        #             "timestamp":1635920767648
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user
        :see: https://api.latoken.com/doc/v2/#tag/Order/operation/getOrderById
        :see: https://api.latoken.com/doc/v2/#tag/StopOrder/operation/getStopOrderById
        :param str [symbol]: not used by latoken fetchOrder
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.trigger]: True if fetching a trigger order
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {
            'id': id,
        }
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['stop', 'trigger'])
        response = None
        if isTrigger:
            response = self.privateGetAuthStopOrderGetOrderId(self.extend(request, params))
        else:
            response = self.privateGetAuthOrderGetOrderId(self.extend(request, params))
        #
        #     {
        #         "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
        #         "status":"ORDER_STATUS_PLACED",
        #         "side":"ORDER_SIDE_BUY",
        #         "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
        #         "type":"ORDER_TYPE_LIMIT",
        #         "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #         "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
        #         "price":"4000.00",
        #         "quantity":"0.01",
        #         "cost":"40.000000000000000000",
        #         "filled":"0",
        #         "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
        #         "creator":"ORDER_CREATOR_USER",
        #         "creatorId":"",
        #         "timestamp":1635920767648
        #     }
        #
        return self.parse_order(response)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://api.latoken.com/doc/v2/#tag/Order/operation/placeOrder
        :see: https://api.latoken.com/doc/v2/#tag/StopOrder/operation/placeStopOrder  # stop
        :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.triggerPrice]: the price at which a trigger order is triggered at
         *
         * EXCHANGE SPECIFIC PARAMETERS
        :param str [params.condition]: "GTC", "IOC", or  "FOK"
        :param str [params.clientOrderId]: [0 .. 50] characters, client's custom order id(free field for your convenience)
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        uppercaseType = type.upper()
        request: dict = {
            'baseCurrency': market['baseId'],
            'quoteCurrency': market['quoteId'],
            'side': side.upper(),  # "BUY", "BID", "SELL", "ASK"
            'condition': 'GTC',  # "GTC", "GOOD_TILL_CANCELLED", "IOC", "IMMEDIATE_OR_CANCEL", "FOK", "FILL_OR_KILL"
            'type': uppercaseType,  # "LIMIT", "MARKET"
            'clientOrderId': self.uuid(),  # 50 characters max
            # 'price': self.price_to_precision(symbol, price),
            # 'quantity': self.amount_to_precision(symbol, amount),
            'quantity': self.amount_to_precision(symbol, amount),
            'timestamp': self.seconds(),
        }
        if uppercaseType == 'LIMIT':
            request['price'] = self.price_to_precision(symbol, price)
        triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
        params = self.omit(params, ['triggerPrice', 'stopPrice'])
        response = None
        if triggerPrice is not None:
            request['stopPrice'] = self.price_to_precision(symbol, triggerPrice)
            response = self.privatePostAuthStopOrderPlace(self.extend(request, params))
        else:
            response = self.privatePostAuthOrderPlace(self.extend(request, params))
        #
        #    {
        #        "baseCurrency": "f7dac554-8139-4ff6-841f-0e586a5984a0",
        #        "quoteCurrency": "a5a7a7a9-e2a3-43f9-8754-29a02f6b709b",
        #        "side": "BID",
        #        "clientOrderId": "my-wonderful-order-number-71566",
        #        "price": "10103.19",
        #        "stopPrice": "10103.19",
        #        "quantity": "3.21",
        #        "timestamp": 1568185507
        #    }
        #
        return self.parse_order(response, market)

    def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://api.latoken.com/doc/v2/#tag/Order/operation/cancelOrder
        :see: https://api.latoken.com/doc/v2/#tag/StopOrder/operation/cancelStopOrder  # stop
        :param str id: order id
        :param str symbol: not used by latoken cancelOrder()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.trigger]: True if cancelling a trigger order
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {
            'id': id,
        }
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['stop', 'trigger'])
        response = None
        if isTrigger:
            response = self.privatePostAuthStopOrderCancel(self.extend(request, params))
        else:
            response = self.privatePostAuthOrderCancel(self.extend(request, params))
        #
        #     {
        #         "id": "12345678-1234-1244-1244-123456789012",
        #         "message": "cancellation request successfully submitted",
        #         "status": "SUCCESS",
        #         "error": "",
        #         "errors": {}
        #     }
        #
        return self.parse_order(response)

    def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders in a market
        :see: https://api.latoken.com/doc/v2/#tag/Order/operation/cancelAllOrders
        :see: https://api.latoken.com/doc/v2/#tag/Order/operation/cancelAllOrdersByPair
        :param str symbol: unified market symbol of the market to cancel orders in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.trigger]: True if cancelling trigger orders
        :returns dict[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {
            # 'currency': market['baseId'],
            # 'quote': market['quoteId'],
        }
        market = None
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['stop', 'trigger'])
        response = None
        if symbol is not None:
            market = self.market(symbol)
            request['currency'] = market['baseId']
            request['quote'] = market['quoteId']
            if isTrigger:
                response = self.privatePostAuthStopOrderCancelAllCurrencyQuote(self.extend(request, params))
            else:
                response = self.privatePostAuthOrderCancelAllCurrencyQuote(self.extend(request, params))
        else:
            if isTrigger:
                response = self.privatePostAuthStopOrderCancelAll(self.extend(request, params))
            else:
                response = self.privatePostAuthOrderCancelAll(self.extend(request, params))
        #
        #     {
        #         "message":"cancellation request successfully submitted",
        #         "status":"SUCCESS"
        #     }
        #
        return [
            self.safe_order({
                'info': response,
            }),
        ]

    def fetch_transactions(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
        """
         * @deprecated
        use fetchDepositsWithdrawals instead
        :see: https://api.latoken.com/doc/v2/#tag/Transaction/operation/getUserTransactions
        :param str code: unified currency code for the currency of the transactions, default is None
        :param int [since]: timestamp in ms of the earliest transaction, default is None
        :param int [limit]: max number of transactions 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>`
        """
        self.load_markets()
        request: dict = {
            # 'page': '1',
            # 'size': 100,
        }
        response = self.privateGetAuthTransaction(self.extend(request, params))
        #
        #     {
        #         "hasNext":false,
        #         "content":[
        #             {
        #                 "id":"fbf7d0d1-2629-4ad8-9def-7a1dba423362",
        #                 "status":"TRANSACTION_STATUS_CONFIRMED",
        #                 "type":"TRANSACTION_TYPE_DEPOSIT",
        #                 "senderAddress":"",
        #                 "recipientAddress":"0x3c46fa2e3f9023bc4897828ed173f8ecb3a554bc",
        #                 "amount":"200.000000000000000000",
        #                 "transactionFee":"0.000000000000000000",
        #                 "timestamp":1635893208404,
        #                 "transactionHash":"0x28bad3b74a042df13d64ddfbca855566a51bf7f190b8cd565c236a18d5cd493f#42",
        #                 "blockHeight":13540262,
        #                 "currency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #                 "memo":null,
        #                 "paymentProvider":"a8d6d1cb-f84a-4e9d-aa82-c6a08b356ee1",
        #                 "requiresCode":false
        #             }
        #         ],
        #         "first":true,
        #         "hasContent":true,
        #         "pageSize":10
        #     }
        #
        currency = None
        if code is not None:
            currency = self.currency(code)
        content = self.safe_list(response, 'content', [])
        return self.parse_transactions(content, currency, since, limit)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        #     {
        #         "id":"fbf7d0d1-2629-4ad8-9def-7a1dba423362",
        #         "status":"TRANSACTION_STATUS_CONFIRMED",
        #         "type":"TRANSACTION_TYPE_DEPOSIT",
        #         "senderAddress":"",
        #         "recipientAddress":"0x3c46fa2e3f9023bc4897828ed173f8ecb3a554bc",
        #         "amount":"200.000000000000000000",
        #         "transactionFee":"0.000000000000000000",
        #         "timestamp":1635893208404,
        #         "transactionHash":"0x28bad3b74a042df13d64ddfbca855566a51bf7f190b8cd565c236a18d5cd493f#42",
        #         "blockHeight":13540262,
        #         "currency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "memo":null,
        #         "paymentProvider":"a8d6d1cb-f84a-4e9d-aa82-c6a08b356ee1",
        #         "requiresCode":false
        #     }
        #
        id = self.safe_string(transaction, 'id')
        timestamp = self.safe_integer(transaction, 'timestamp')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        amount = self.safe_number(transaction, 'amount')
        addressFrom = self.safe_string(transaction, 'senderAddress')
        addressTo = self.safe_string(transaction, 'recipientAddress')
        txid = self.safe_string(transaction, 'transactionHash')
        tagTo = self.safe_string(transaction, 'memo')
        fee = {
            'currency': None,
            'cost': None,
            'rate': None,
        }
        feeCost = self.safe_number(transaction, 'transactionFee')
        if feeCost is not None:
            fee['cost'] = feeCost
            fee['currency'] = code
        type = self.parse_transaction_type(self.safe_string(transaction, 'type'))
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'network': None,
            'addressFrom': addressFrom,
            'addressTo': addressTo,
            'address': addressTo,
            'tagFrom': None,
            'tagTo': tagTo,
            'tag': tagTo,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': None,
            'comment': None,
            'internal': None,
            'fee': fee,
        }

    def parse_transaction_status(self, status: Str):
        statuses: dict = {
            'TRANSACTION_STATUS_CONFIRMED': 'ok',
            'TRANSACTION_STATUS_EXECUTED': 'ok',
            'TRANSACTION_STATUS_CANCELLED': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction_type(self, type):
        types: dict = {
            'TRANSACTION_TYPE_DEPOSIT': 'deposit',
            'TRANSACTION_TYPE_WITHDRAWAL': 'withdrawal',
        }
        return self.safe_string(types, type, type)

    def fetch_transfers(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> TransferEntries:
        """
        fetch a history of internal transfers made on an account
        :see: https://api.latoken.com/doc/v2/#tag/Transfer/operation/getUsersTransfers
        :param str code: unified currency code of the currency transferred
        :param int [since]: the earliest time in ms to fetch transfers for
        :param int [limit]: the maximum number of  transfers structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transfer structures <https://docs.ccxt_zagrava.com/#/?id=transfer-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        response = self.privateGetAuthTransfer(params)
        #
        #     {
        #         "hasNext": True,
        #         "content": [
        #             {
        #             "id": "ebd6312f-cb4f-45d1-9409-4b0b3027f21e",
        #             "status": "TRANSFER_STATUS_COMPLETED",
        #             "type": "TRANSFER_TYPE_WITHDRAW_SPOT",
        #             "fromAccount": "c429c551-adbb-4078-b74b-276bea308a36",
        #             "toAccount": "631c6203-bd62-4734-a04d-9b2a951f43b9",
        #             "transferringFunds": 1259.0321785,
        #             "usdValue": 1259.032179,
        #             "rejectReason": null,
        #             "timestamp": 1633515579530,
        #             "direction": "INTERNAL",
        #             "method": "TRANSFER_METHOD_UNKNOWN",
        #             "recipient": null,
        #             "sender": null,
        #             "currency": "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #             "codeRequired": False,
        #             "fromUser": "ce555f3f-585d-46fb-9ae6-487f66738073",
        #             "toUser": "ce555f3f-585d-46fb-9ae6-487f66738073",
        #             "fee": 0
        #             },
        #             ...
        #         ],
        #         "first": True,
        #         "pageSize": 20,
        #         "hasContent": True
        #     }
        #
        transfers = self.safe_list(response, 'content', [])
        return self.parse_transfers(transfers, currency, since, limit)

    def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        transfer currency internally between wallets on the same account
        :see: https://api.latoken.com/doc/v2/#tag/Transfer/operation/transferByEmail
        :see: https://api.latoken.com/doc/v2/#tag/Transfer/operation/transferById
        :see: https://api.latoken.com/doc/v2/#tag/Transfer/operation/transferByPhone
        :param str code: unified currency code
        :param float amount: amount to transfer
        :param str fromAccount: account to transfer from
        :param str toAccount: account to transfer to
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transfer structure <https://docs.ccxt_zagrava.com/#/?id=transfer-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
            'recipient': toAccount,
            'value': self.currency_to_precision(code, amount),
        }
        response = None
        if toAccount.find('@') >= 0:
            response = self.privatePostAuthTransferEmail(self.extend(request, params))
        elif len(toAccount) == 36:
            response = self.privatePostAuthTransferId(self.extend(request, params))
        else:
            response = self.privatePostAuthTransferPhone(self.extend(request, params))
        #
        #     {
        #         "id": "e6fc4ace-7750-44e4-b7e9-6af038ac7107",
        #         "status": "TRANSFER_STATUS_COMPLETED",
        #         "type": "TRANSFER_TYPE_DEPOSIT_SPOT",
        #         "fromAccount": "3bf61015-bf32-47a6-b237-c9f70df772ad",
        #         "toAccount": "355eb279-7c7e-4515-814a-575a49dc0325",
        #         "transferringFunds": "500000.000000000000000000",
        #         "usdValue": "0.000000000000000000",
        #         "rejectReason": "",
        #         "timestamp": 1576844438402,
        #         "direction": "INTERNAL",
        #         "method": "TRANSFER_METHOD_UNKNOWN",
        #         "recipient": "",
        #         "sender": "",
        #         "currency": "40af7879-a8cc-4576-a42d-7d2749821b58",
        #         "codeRequired": False,
        #         "fromUser": "cd555555-666d-46fb-9ae6-487f66738073",
        #         "toUser": "cd555555-666d-46fb-9ae6-487f66738073",
        #         "fee": 0
        #     }
        #
        return self.parse_transfer(response)

    def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
        #
        #     {
        #         "id": "e6fc4ace-7750-44e4-b7e9-6af038ac7107",
        #         "status": "TRANSFER_STATUS_COMPLETED",
        #         "type": "TRANSFER_TYPE_DEPOSIT_SPOT",
        #         "fromAccount": "3bf61015-bf32-47a6-b237-c9f70df772ad",
        #         "toAccount": "355eb279-7c7e-4515-814a-575a49dc0325",
        #         "transferringFunds": "500000.000000000000000000",
        #         "usdValue": "0.000000000000000000",
        #         "rejectReason": "",
        #         "timestamp": 1576844438402,
        #         "direction": "INTERNAL",
        #         "method": "TRANSFER_METHOD_UNKNOWN",
        #         "recipient": "",
        #         "sender": "",
        #         "currency": "40af7879-a8cc-4576-a42d-7d2749821b58",
        #         "codeRequired": False,
        #         "fromUser": "cd555555-666d-46fb-9ae6-487f66738073",
        #         "toUser": "cd555555-666d-46fb-9ae6-487f66738073",
        #         "fee": 0
        #     }
        #
        timestamp = self.safe_timestamp(transfer, 'timestamp')
        currencyId = self.safe_string(transfer, 'currency')
        status = self.safe_string(transfer, 'status')
        return {
            'info': transfer,
            'id': self.safe_string(transfer, 'id'),
            'timestamp': self.safe_integer(transfer, 'timestamp'),
            'datetime': self.iso8601(timestamp),
            'currency': self.safe_currency_code(currencyId, currency),
            'amount': self.safe_number(transfer, 'transferringFunds'),
            'fromAccount': self.safe_string(transfer, 'fromAccount'),
            'toAccount': self.safe_string(transfer, 'toAccount'),
            'status': self.parse_transfer_status(status),
        }

    def parse_transfer_status(self, status: Str) -> Str:
        statuses: dict = {
            'TRANSFER_STATUS_COMPLETED': 'ok',
            'TRANSFER_STATUS_PENDING': 'pending',
            'TRANSFER_STATUS_REJECTED': 'failed',
            'TRANSFER_STATUS_UNVERIFIED': 'pending',
            'TRANSFER_STATUS_CANCELLED': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def sign(self, path, api='public', method='GET', params=None, headers=None, body=None):
        request = '/' + self.version + '/' + self.implode_params(path, params)
        requestString = request
        query = self.omit(params, self.extract_params(path))
        urlencodedQuery = self.urlencode(query)
        if method == 'GET':
            if query:
                requestString += '?' + urlencodedQuery
        if api == 'private':
            self.check_required_credentials()
            auth = method + request + urlencodedQuery
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha512)
            headers = {
                'X-LA-APIKEY': self.apiKey,
                'X-LA-SIGNATURE': signature,
                'X-LA-DIGEST': 'HMAC-SHA512',  # HMAC-SHA384, HMAC-SHA512, optional
            }
            if method == 'POST':
                headers['Content-Type'] = 'application/json'
                body = self.json(query)
        url = self.urls['api']['rest'] + requestString
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if not response:
            return None
        #
        # {"result":false,"message":"invalid API key, signature or digest","error":"BAD_REQUEST","status":"FAILURE"}
        # {"result":false,"message":"request expired or bad <timeAlive>/<timestamp> format","error":"BAD_REQUEST","status":"FAILURE"}
        # {"message":"Internal Server Error","error":"INTERNAL_ERROR","status":"FAILURE"}
        # {"result":false,"message":"Internal error","error":"For input string: \"NaN\"","status":"FAILURE"}
        #
        message = self.safe_string(response, 'message')
        feedback = self.id + ' ' + body
        if message is not None:
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
        error = self.safe_value(response, 'error')
        errorMessage = self.safe_string(error, 'message')
        if (error is not None) or (errorMessage is not None):
            self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
            raise ExchangeError(feedback)  # unknown message
        return None
