# -*- 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.coinmetro import ImplicitAPI
from ccxt_zagrava.base.types import Balances, Currencies, Currency, IndexType, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import PermissionDenied
from ccxt_zagrava.base.errors import ArgumentsRequired
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import BadSymbol
from ccxt_zagrava.base.errors import InsufficientFunds
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import OrderNotFound
from ccxt_zagrava.base.errors import RateLimitExceeded
from ccxt_zagrava.base.decimal_to_precision import TICK_SIZE
from ccxt_zagrava.base.precise import Precise


class coinmetro(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(coinmetro, self).describe(), {
            'id': 'coinmetro',
            'name': 'Coinmetro',
            'countries': ['EE'],  # Republic of Estonia
            'version': 'v1',
            'rateLimit': 200,  # 1 request per 200 ms, 20 per minute, 300 per hour, 1k per day
            'certified': False,
            'pro': False,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': True,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': False,
                'borrowCrossMargin': True,
                'borrowIsolatedMargin': False,
                'cancelAllOrders': False,
                'cancelOrder': True,
                'cancelOrders': False,
                'closeAllPositions': False,
                'closePosition': True,
                'createDepositAddress': False,
                'createOrder': True,
                'createPostOnlyOrder': False,
                'createReduceOnlyOrder': False,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'deposit': False,
                'editOrder': False,
                'fetchAccounts': False,
                'fetchBalance': True,
                'fetchBidsAsks': True,
                'fetchBorrowInterest': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchCanceledAndClosedOrders': True,
                'fetchCanceledOrders': False,
                'fetchClosedOrder': False,
                'fetchClosedOrders': False,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': True,
                'fetchDeposit': False,
                'fetchDepositAddress': False,
                'fetchDepositAddresses': False,
                'fetchDepositAddressesByNetwork': False,
                'fetchDeposits': False,
                'fetchDepositsWithdrawals': False,
                'fetchDepositWithdrawFee': False,
                'fetchDepositWithdrawFees': False,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchL3OrderBook': False,
                'fetchLedger': True,
                'fetchLeverage': False,
                'fetchLeverageTiers': False,
                'fetchMarketLeverageTiers': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrder': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderBooks': False,
                'fetchOrders': False,
                'fetchOrderTrades': False,
                'fetchPosition': False,
                'fetchPositions': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchStatus': False,
                'fetchTicker': False,
                'fetchTickers': True,
                'fetchTime': False,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': False,
                'fetchTradingLimits': False,
                'fetchTransactionFee': False,
                'fetchTransactionFees': False,
                'fetchTransactions': False,
                'fetchTransfers': False,
                'fetchWithdrawal': False,
                'fetchWithdrawals': False,
                'fetchWithdrawalWhitelist': False,
                'reduceMargin': False,
                'repayCrossMargin': False,
                'repayIsolatedMargin': False,
                'sandbox': True,
                'setLeverage': False,
                'setMargin': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'signIn': False,
                'transfer': False,
                'withdraw': False,
                'ws': False,
            },
            'timeframes': {
                '1m': '60000',
                '5m': '300000',
                '30m': '1800000',
                '4h': '14400000',
                '1d': '86400000',
            },
            'urls': {
                'logo': 'https://github.com/ccxt_zagrava/ccxt_zagrava/assets/43336371/e86f87ec-6ba3-4410-962b-f7988c5db539',
                'api': {
                    'public': 'https://api.coinmetro.com',
                    'private': 'https://api.coinmetro.com',
                },
                'test': {
                    'public': 'https://api.coinmetro.com/open',
                    'private': 'https://api.coinmetro.com/open',
                },
                'www': 'https://coinmetro.com/',
                'doc': [
                    'https://documenter.getpostman.com/view/3653795/SVfWN6KS',
                ],
                'fees': 'https://help.coinmetro.com/hc/en-gb/articles/6844007317789-What-are-the-fees-on-Coinmetro-',
                'referral': 'https://go.coinmetro.com/?ref=crypto24',
            },
            'api': {
                'public': {
                    'get': {
                        'demo/temp': 1,
                        'exchange/candles/{pair}/{timeframe}/{from}/{to}': 3,
                        'exchange/prices': 1,
                        'exchange/ticks/{pair}/{from}': 3,
                        'assets': 1,
                        'markets': 1,
                        'exchange/book/{pair}': 3,
                        'exchange/bookUpdates/{pair}/{from}': 1,  # not unified
                    },
                },
                'private': {
                    'get': {
                        'users/balances': 1,
                        'users/wallets': 1,
                        'users/wallets/history/{since}': 1.67,
                        'exchange/orders/status/{orderID}': 1,
                        'exchange/orders/active': 1,
                        'exchange/orders/history/{since}': 1.67,
                        'exchange/fills/{since}': 1.67,
                        'exchange/margin': 1,  # not unified
                    },
                    'post': {
                        'jwt': 1,  # not unified
                        'jwtDevice': 1,  # not unified
                        'devices': 1,  # not unified
                        'jwt-read-only': 1,  # not unified
                        'exchange/orders/create': 1,
                        'exchange/orders/modify/{orderID}': 1,  # not unified
                        'exchange/swap': 1,  # not unified
                        'exchange/swap/confirm/{swapId}': 1,  # not unified
                        'exchange/orders/close/{orderID}': 1,
                        'exchange/orders/hedge': 1,  # not unified
                    },
                    'put': {
                        'jwt': 1,  # not unified
                        'exchange/orders/cancel/{orderID}': 1,
                        'users/margin/collateral': 1,
                        'users/margin/primary/{currency}': 1,  # not unified
                    },
                },
            },
            'requiredCredentials': {
                'apiKey': False,
                'secret': False,
                'uid': True,
                'token': True,
            },
            'fees': {
                'trading': {
                    'feeSide': 'get',
                    'tierBased': False,
                    'percentage': True,
                    'taker': self.parse_number('0.001'),
                    'maker': self.parse_number('0'),
                },
            },
            'precisionMode': TICK_SIZE,
            # exchange-specific options
            'options': {
                'currenciesByIdForParseMarket': None,
                'currencyIdsListForParseMarket': None,
            },
            'exceptions': {
                # https://trade-docs.coinmetro.co/?javascript--nodejs#message-codes
                'exact': {
                    'Both buyingCurrency and sellingCurrency are required': InvalidOrder,  # 422 - "Both buyingCurrency and sellingCurrency are required"
                    'One and only one of buyingQty and sellingQty is required': InvalidOrder,  # 422 - "One and only one of buyingQty and sellingQty is required"
                    'Invalid buyingCurrency': InvalidOrder,  # 422 - "Invalid buyingCurrency"
                    'Invalid \'from\'': BadRequest,  # 422 Unprocessable Entity {"message":"Invalid 'from'"}
                    'Invalid sellingCurrency': InvalidOrder,  # 422 - "Invalid sellingCurrency"
                    'Invalid buyingQty': InvalidOrder,  # 422 - "Invalid buyingQty"
                    'Invalid sellingQty': InvalidOrder,  # 422 - "Invalid sellingQty"
                    'Insufficient balance': InsufficientFunds,  # 422 - "Insufficient balance"
                    'Expiration date is in the past or too near in the future': InvalidOrder,  # 422 Unprocessable Entity {"message":"Expiration date is in the past or too near in the future"}
                    'Forbidden': PermissionDenied,  # 403 Forbidden {"message":"Forbidden"}
                    'Order Not Found': OrderNotFound,  # 404 Not Found {"message":"Order Not Found"}
                    'since must be a millisecond timestamp': BadRequest,  # 422 Unprocessable Entity {"message":"since must be a millisecond timestamp"}
                    'This pair is disabled on margin': BadSymbol,  # 422 Unprocessable Entity {"message":"This pair is disabled on margin"}
                },
                'broad': {
                    'accessing from a new IP': PermissionDenied,  # 403 Forbidden {"message":"You're accessing from a new IP. Please check your email."}
                    'available to allocate': InsufficientFunds,  # 403 Forbidden {"message":"Insufficient EUR available to allocate"}
                    'At least': BadRequest,  # 422 Unprocessable Entity {"message":"At least 5 EUR per operation"}
                    'collateral is not allowed': BadRequest,  # 422 Unprocessable Entity {"message":"DOGE collateral is not allowed"}
                    'Insufficient liquidity': InvalidOrder,  # 503 Service Unavailable {"message":"Insufficient liquidity to fill the FOK order completely."}
                    'Insufficient order size': InvalidOrder,  # 422 Unprocessable Entity {"message":"Insufficient order size - min 0.002 ETH"}
                    'Invalid quantity': InvalidOrder,  # 422 Unprocessable Entity {"message":"Invalid quantity!"}
                    'Invalid Stop Loss': InvalidOrder,  # 422 Unprocessable Entity {"message":"Invalid Stop Loss!"}
                    'Invalid stop price!': InvalidOrder,  # 422 Unprocessable Entity {"message":"Invalid stop price!"}
                    'Not enough balance': InsufficientFunds,  # 422 Unprocessable Entity {"message":"Not enough balance!"}
                    'Not enough margin': InsufficientFunds,  # Unprocessable Entity {"message":"Not enough margin!"}
                    'orderType missing': BadRequest,  # 422 Unprocessable Entity {"message":"orderType missing!"}
                    'Server Timeout': ExchangeError,  # 503 Service Unavailable {"message":"Server Timeout!"}
                    'Time in force has to be IOC or FOK for market orders': InvalidOrder,  # 422 Unprocessable Entity {"message":"Time in force has to be IOC or FOK for market orders!"}
                    'Too many attempts': RateLimitExceeded,  # 429 Too Many Requests {"message":"Too many attempts. Try again in 3 seconds"}
                },
            },
        })

    def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#d5876d43-a3fe-4479-8c58-24d0f044edfb
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = self.publicGetAssets(params)
        #
        #     [
        #         {
        #             "symbol": "BTC",
        #             "name": "Bitcoin",
        #             "color": "#FFA500",
        #             "type": "coin",
        #             "canDeposit": True,
        #             "canWithdraw": True,
        #             "canTrade": True,
        #             "notabeneDecimals": 8,
        #             "canMarket": True,
        #             "maxSwap": 10000,
        #             "digits": 6,
        #             "multiplier": 1000000,
        #             "bookDigits": 8,
        #             "bookMultiplier": 100000000,
        #             "sentimentData": {
        #                 "sentiment": 51.59555555555555,
        #                 "interest": 1.127511216044664
        #             },
        #             "minQty": 0.0001
        #         },
        #         {
        #             "symbol": "EUR",
        #             "name": "Euro",
        #             "color": "#1246FF",
        #             "type": "fiat",
        #             "canDeposit": True,
        #             "canWithdraw": True,
        #             "canTrade": True,
        #             "canMarket": True,
        #             "maxSwap": 10000,
        #             "digits": 2,
        #             "multiplier": 100,
        #             "bookDigits": 3,
        #             "bookMultiplier": 1000,
        #             "minQty": 5
        #         }
        #         ...
        #     ]
        #
        result: dict = {}
        for i in range(0, len(response)):
            currency = response[i]
            id = self.safe_string(currency, 'symbol')
            code = self.safe_currency_code(id)
            withdraw = self.safe_value(currency, 'canWithdraw')
            deposit = self.safe_value(currency, 'canDeposit')
            canTrade = self.safe_value(currency, 'canTrade')
            active = withdraw if canTrade else True
            minAmount = self.safe_number(currency, 'minQty')
            result[code] = self.safe_currency_structure({
                'id': id,
                'code': code,
                'name': code,
                'info': currency,
                'active': active,
                'deposit': deposit,
                'withdraw': withdraw,
                'fee': None,
                'precision': self.parse_number(self.parse_precision(self.safe_string(currency, 'digits'))),
                'limits': {
                    'amount': {'min': minAmount, 'max': None},
                    'withdraw': {'min': None, 'max': None},
                },
                'networks': {},
            })
        if self.safe_value(self.options, 'currenciesByIdForParseMarket') is None:
            currenciesById = self.index_by(result, 'id')
            self.options['currenciesByIdForParseMarket'] = currenciesById
            self.options['currencyIdsListForParseMarket'] = list(currenciesById.keys())
        return result

    def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for coinmetro
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#9fd18008-338e-4863-b07d-722878a46832
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = self.publicGetMarkets(params)
        if self.safe_value(self.options, 'currenciesByIdForParseMarket') is None:
            self.fetch_currencies()
        #
        #     [
        #         {
        #             "pair": "YFIEUR",
        #             "precision": 5,
        #             "margin": False
        #         },
        #         {
        #             "pair": "BTCEUR",
        #             "precision": 2,
        #             "margin": True
        #         },
        #         ...
        #     ]
        #
        return self.parse_markets(response)

    def parse_market(self, market: dict) -> Market:
        id = self.safe_string(market, 'pair')
        parsedMarketId = self.parse_market_id(id)
        baseId = self.safe_string(parsedMarketId, 'baseId')
        quoteId = self.safe_string(parsedMarketId, 'quoteId')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        basePrecisionAndLimits = self.parse_market_precision_and_limits(baseId)
        quotePrecisionAndLimits = self.parse_market_precision_and_limits(quoteId)
        margin = self.safe_bool(market, 'margin', False)
        tradingFees = self.safe_value(self.fees, 'trading', {})
        return self.safe_market_structure({
            'id': id,
            'symbol': base + '/' + quote,
            'base': base,
            'quote': quote,
            'settle': None,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': None,
            'type': 'spot',
            'spot': True,
            'margin': margin,
            'swap': False,
            'future': False,
            'option': False,
            'active': True,
            'contract': False,
            'linear': None,
            'inverse': None,
            'taker': self.safe_number(tradingFees, 'taker'),
            'maker': self.safe_number(tradingFees, 'maker'),
            'contractSize': None,
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'precision': {
                'amount': basePrecisionAndLimits['precision'],
                'price': self.parse_number(self.parse_precision(self.safe_string(market, 'precision'))),
            },
            'limits': {
                'leverage': {
                    'min': None,
                    'max': None,
                },
                'amount': {
                    'min': basePrecisionAndLimits['minLimit'],
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': quotePrecisionAndLimits['minLimit'],
                    'max': None,
                },
            },
            'created': None,
            'info': market,
        })

    def parse_market_id(self, marketId):
        baseId = None
        quoteId = None
        currencyIds = self.safe_value(self.options, 'currencyIdsListForParseMarket', [])
        for i in range(0, len(currencyIds)):
            currencyId = currencyIds[i]
            entryIndex = marketId.find(currencyId)
            if entryIndex != -1:
                restId = marketId.replace(currencyId, '')
                if self.in_array(restId, currencyIds):
                    if entryIndex == 0:
                        baseId = currencyId
                        quoteId = restId
                    else:
                        baseId = restId
                        quoteId = currencyId
                    break
        result: dict = {
            'baseId': baseId,
            'quoteId': quoteId,
        }
        return result

    def parse_market_precision_and_limits(self, currencyId):
        currencies = self.safe_value(self.options, 'currenciesByIdForParseMarket', {})
        currency = self.safe_value(currencies, currencyId, {})
        limits = self.safe_value(currency, 'limits', {})
        amountLimits = self.safe_value(limits, 'amount', {})
        minLimit = self.safe_number(amountLimits, 'min')
        result: dict = {
            'precision': self.safe_number(currency, 'precision'),
            'minLimit': minLimit,
        }
        return result

    def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#13cfb5bc-7bfb-4847-85e1-e0f35dfb3573
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: the latest time in ms to fetch entries for
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
            'timeframe': self.safe_string(self.timeframes, timeframe, timeframe),
        }
        until = None
        if since is not None:
            request['from'] = since
            if limit is not None:
                duration = self.parse_timeframe(timeframe) * 1000
                until = self.sum(since, duration * (limit))
        else:
            request['from'] = ':from'  # self endpoint doesn't accept empty from and to params(setting them into the value described in the documentation)
        until = self.safe_integer(params, 'until', until)
        if until is not None:
            params = self.omit(params, ['until'])
            request['to'] = until
        else:
            request['to'] = ':to'
        response = self.publicGetExchangeCandlesPairTimeframeFromTo(self.extend(request, params))
        #
        #     {
        #         "candleHistory": [
        #             {
        #                 "pair": "ETHUSDT",
        #                 "timeframe": 86400000,
        #                 "timestamp": 1697673600000,
        #                 "c": 1567.4409353098604,
        #                 "h": 1566.7514068472303,
        #                 "l": 1549.4563666936847,
        #                 "o": 1563.4490341395904,
        #                 "v": 0
        #             },
        #             {
        #                 "pair": "ETHUSDT",
        #                 "timeframe": 86400000,
        #                 "timestamp": 1697760000000,
        #                 "c": 1603.7831363339324,
        #                 "h": 1625.0356823666407,
        #                 "l": 1565.4629390011505,
        #                 "o": 1566.8387619426028,
        #                 "v": 0
        #             },
        #             ...
        #         ]
        #     }
        #
        candleHistory = self.safe_list(response, 'candleHistory', [])
        return self.parse_ohlcvs(candleHistory, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        return [
            self.safe_integer(ohlcv, 'timestamp'),
            self.safe_number(ohlcv, 'o'),
            self.safe_number(ohlcv, 'h'),
            self.safe_number(ohlcv, 'l'),
            self.safe_number(ohlcv, 'c'),
            self.safe_number(ohlcv, 'v'),
        ]

    def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#6ee5d698-06da-4570-8c84-914185e05065
        :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(default 200, max 500)
        :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 = {
            'pair': market['id'],
        }
        if since is not None:
            request['from'] = since
        else:
            # self endpoint accepts empty from param
            request['from'] = ''
        response = self.publicGetExchangeTicksPairFrom(self.extend(request, params))
        #
        #     {
        #         "tickHistory": [
        #             {
        #                 "pair": "ETHUSDT",
        #                 "price": 2077.5623,
        #                 "qty": 0.002888,
        #                 "timestamp": 1700684689420,
        #                 "seqNum": 10644554718
        #             },
        #             {
        #                 "pair": "ETHUSDT",
        #                 "price": 2078.3848,
        #                 "qty": 0.003368,
        #                 "timestamp": 1700684738410,
        #                 "seqNum": 10644559561
        #             },
        #             {
        #                 "pair": "ETHUSDT",
        #                 "price": 2077.1513,
        #                 "qty": 0.00337,
        #                 "timestamp": 1700684816853,
        #                 "seqNum": 10644567113
        #             },
        #             ...
        #         ]
        #     }
        #
        tickHistory = self.safe_list(response, 'tickHistory', [])
        return self.parse_trades(tickHistory, market, since, limit)

    def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#4d48ae69-8ee2-44d1-a268-71f84e557b7b
        :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(default 500, max 1000)
        :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()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request: dict = {}
        if since is not None:
            request['since'] = since
        else:
            # the exchange requires a value for the since param
            request['since'] = 0
        response = self.privateGetExchangeFillsSince(self.extend(request, params))
        #
        #     [
        #         {
        #             "pair": "ETHUSDC",
        #             "seqNumber": 10873722343,
        #             "timestamp": 1702570610747,
        #             "qty": 0.002,
        #             "price": 2282,
        #             "side": "buy",
        #             "orderID": "65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c"
        #         },
        #         ...
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # fetchTrades
        #     {
        #         "pair": "ETHUSDT",
        #         "price": 2077.1513,
        #         "qty": 0.00337,
        #         "timestamp": 1700684816853,
        #         "seqNum": 10644567113
        #     },
        #
        # fetchMyTrades
        #     {
        #         "pair": "ETHUSDC",
        #         "seqNumber": 10873722343,
        #         "timestamp": 1702570610747,
        #         "qty": 0.002,
        #         "price": 2282,
        #         "side": "buy",
        #         "orderID": "65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c"
        #     }
        #
        # fetchOrders
        #     {
        #         "_id": "657b31d360a9542449381bdc",
        #         "seqNumber": 10873722343,
        #         "timestamp": 1702570610747,
        #         "qty": 0.002,
        #         "price": 2282,
        #         "side": "buy"
        #     }
        #
        #    {
        #        "pair":"ETHUSDC",
        #        "seqNumber":"10873722343",
        #        "timestamp":"1702570610747",
        #        "qty":"0.002",
        #        "price":"2282",
        #        "side":"buy",
        #        "orderID":"65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c",
        #        "userID":"65671262d93d9525ac009e36"
        #     }
        #
        marketId = self.safe_string_2(trade, 'symbol', 'pair')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        id = self.safe_string_n(trade, ['_id', 'seqNum', 'seqNumber'])
        timestamp = self.safe_integer(trade, 'timestamp')
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'qty')
        order = self.safe_string(trade, 'orderID')
        side = self.safe_string(trade, 'side')
        return self.safe_trade({
            'id': id,
            'order': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': None,
            'side': side,
            'takerOrMaker': None,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': None,
            'info': trade,
        }, market)

    def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#26ad80d7-8c46-41b5-9208-386f439a8b87
        :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(default 100, max 200)
        :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 = {
            'pair': market['id'],
        }
        response = self.publicGetExchangeBookPair(self.extend(request, params))
        #
        #     {
        #         "book": {
        #             "pair": "ETHUSDT",
        #             "seqNumber": 10800409239,
        #             "ask": {
        #                 "2354.2861": 3.75,
        #                 "2354.3138": 19,
        #                 "2354.7538": 80,
        #                 "2355.5430": 260,
        #                 "2356.4611": 950,
        #                 "2361.7150": 1500,
        #                 "206194.0000": 0.01
        #             },
        #             "bid": {
        #                 "2352.6339": 3.75,
        #                 "2352.6002": 19,
        #                 "2352.2402": 80,
        #                 "2351.4582": 260,
        #                 "2349.3111": 950,
        #                 "2343.8601": 1500,
        #                 "1.0000": 5
        #             },
        #             "checksum": 2108177337
        #         }
        #     }
        #
        book = self.safe_value(response, 'book', {})
        rawBids = self.safe_value(book, 'bid', {})
        rawAsks = self.safe_value(book, 'ask', {})
        rawOrderbook: dict = {
            'bids': rawBids,
            'asks': rawAsks,
        }
        orderbook = self.parse_order_book(rawOrderbook, symbol)
        orderbook['nonce'] = self.safe_integer(book, 'seqNumber')
        return orderbook

    def parse_bids_asks(self, bidasks, priceKey: IndexType = 0, amountKey: IndexType = 1, countOrIdKey: IndexType = 2):
        prices = list(bidasks.keys())
        result = []
        for i in range(0, len(prices)):
            priceString = self.safe_string(prices, i)
            price = self.safe_number(prices, i)
            volume = self.safe_number(bidasks, priceString)
            result.append([price, volume])
        return result

    def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#6ecd1cd1-f162-45a3-8b3b-de690332a485
        :param str[] [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.publicGetExchangePrices(params)
        #
        #     {
        #         "latestPrices": [
        #             {
        #                 "pair": "PERPEUR",
        #                 "timestamp": 1702549840393,
        #                 "price": 0.7899997816001223,
        #                 "qty": 1e-12,
        #                 "ask": 0.8,
        #                 "bid": 0.7799995632002446
        #             },
        #             {
        #                 "pair": "PERPUSD",
        #                 "timestamp": 1702549841973,
        #                 "price": 0.8615317721366659,
        #                 "qty": 1e-12,
        #                 "ask": 0.8742333599999257,
        #                 "bid": 0.8490376365388491
        #             },
        #             ...
        #         ],
        #         "24hInfo": [
        #             {
        #                 "delta": 0.25396444229149906,
        #                 "h": 0.78999978160012,
        #                 "l": 0.630001740844,
        #                 "v": 54.910000002833996,
        #                 "pair": "PERPEUR",
        #                 "sentimentData": {
        #                     "sentiment": 36.71333333333333,
        #                     "interest": 0.47430830039525695
        #                     }
        #                 },
        #             {
        #                 "delta": 0.26915154078134096,
        #                 "h": 0.86220315458898,
        #                 "l": 0.67866757035154,
        #                 "v": 2.835000000000001e-9,
        #                 "pair": "PERPUSD",
        #                 "sentimentData": {
        #                     "sentiment": 36.71333333333333,
        #                     "interest": 0.47430830039525695
        #                 }
        #             },
        #             ...
        #         ]
        #     }
        #
        latestPrices = self.safe_value(response, 'latestPrices', [])
        twentyFourHInfos = self.safe_value(response, '24hInfo', [])
        tickersObject: dict = {}
        # merging info from two lists into one
        for i in range(0, len(latestPrices)):
            latestPrice = latestPrices[i]
            marketId = self.safe_string(latestPrice, 'pair')
            if marketId is not None:
                tickersObject[marketId] = latestPrice
        for i in range(0, len(twentyFourHInfos)):
            twentyFourHInfo = twentyFourHInfos[i]
            marketId = self.safe_string(twentyFourHInfo, 'pair')
            if marketId is not None:
                latestPrice = self.safe_value(tickersObject, marketId, {})
                tickersObject[marketId] = self.extend(twentyFourHInfo, latestPrice)
        tickers = list(tickersObject.values())
        return self.parse_tickers(tickers, symbols)

    def fetch_bids_asks(self, symbols: Strings = None, params={}):
        """
        fetches the bid and ask price and volume for multiple markets
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#6ecd1cd1-f162-45a3-8b3b-de690332a485
        :param str[] [symbols]: unified symbols of the markets to fetch the bids and asks for, all markets 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.publicGetExchangePrices(params)
        latestPrices = self.safe_list(response, 'latestPrices', [])
        return self.parse_tickers(latestPrices, symbols)

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #     {
        #         "pair": "PERPUSD",
        #         "timestamp": 1702549841973,
        #         "price": 0.8615317721366659,
        #         "qty": 1e-12,
        #         "ask": 0.8742333599999257,
        #         "bid": 0.8490376365388491
        #         "delta": 0.26915154078134096,
        #         "h": 0.86220315458898,
        #         "l": 0.67866757035154,
        #         "v": 2.835000000000001e-9,
        #         "sentimentData": {
        #             "sentiment": 36.71333333333333,
        #             "interest": 0.47430830039525695
        #         }
        #     }
        #
        marketId = self.safe_string(ticker, 'pair')
        market = self.safe_market(marketId, market)
        timestamp = self.safe_integer(ticker, 'timestamp')
        bid = self.safe_string(ticker, 'bid')
        ask = self.safe_string(ticker, 'ask')
        high = self.safe_string(ticker, 'h')
        low = self.safe_string(ticker, 'l')
        last = self.safe_string(ticker, 'price')
        baseVolume = self.safe_string(ticker, 'v')
        delta = self.safe_string(ticker, 'delta')
        percentage = Precise.string_mul(delta, '100')
        return self.safe_ticker({
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'open': None,
            'high': high,
            'low': low,
            'close': None,
            'last': last,
            'bid': bid,
            'bidVolume': None,
            'ask': ask,
            'askVolume': None,
            'vwap': None,
            'previousClose': None,
            'change': None,
            'percentage': percentage,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': None,
            'info': ticker,
        }, market)

    def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#741a1dcc-7307-40d0-acca-28d003d1506a
        :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.privateGetUsersWallets(params)
        list = self.safe_list(response, 'list', [])
        return self.parse_balance(list)

    def parse_balance(self, balances) -> Balances:
        #
        #     [
        #         {
        #             "xcmLocks": [],
        #             "xcmLockAmounts": [],
        #             "refList": [],
        #             "balanceHistory": [],
        #             "_id": "5fecd3c998e75c2e4d63f7c3",
        #             "currency": "BTC",
        #             "label": "BTC",
        #             "userId": "5fecd3c97fbfed1521db23bd",
        #             "__v": 0,
        #             "balance": 0.5,
        #             "createdAt": "2020-12-30T19:23:53.646Z",
        #             "disabled": False,
        #             "updatedAt": "2020-12-30T19:23:53.653Z",
        #             "reserved": 0,
        #             "id": "5fecd3c998e75c2e4d63f7c3"
        #         },
        #         ...
        #     ]
        #
        result: dict = {
            'info': balances,
        }
        for i in range(0, len(balances)):
            balanceEntry = self.safe_dict(balances, i, {})
            currencyId = self.safe_string(balanceEntry, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['total'] = self.safe_string(balanceEntry, 'balance')
            account['used'] = self.safe_string(balanceEntry, 'reserved')
            result[code] = account
        return self.safe_balance(result)

    def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch the history of changes, actions done by the user or operations that altered balance of the user
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#4e7831f7-a0e7-4c3e-9336-1d0e5dcb15cf
        :param str code: unified currency code, default is None
        :param int [since]: timestamp in ms of the earliest ledger entry, default is None
        :param int [limit]: max number of ledger entrys to return(default 200, max 500)
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: the latest time in ms to fetch entries for
        :returns dict: a `ledger structure <https://docs.ccxt_zagrava.com/#/?id=ledger-structure>`
        """
        self.load_markets()
        request: dict = {}
        if since is not None:
            request['since'] = since
        else:
            # self endpoint accepts empty since param
            request['since'] = ''
        currency = None
        if code is not None:
            currency = self.currency(code)
        response = self.privateGetUsersWalletsHistorySince(self.extend(request, params))
        #
        #     {
        #         "list": [
        #             {
        #                 "currency": "USDC",
        #                 "label": "USDC",
        #                 "userId": "65671262d93d9525ac009e36",
        #                 "balance": 0,
        #                 "disabled": False,
        #                 "balanceHistory": [
        #                     {
        #                         "description": "Deposit - 657973a9b6eadf0f33d70100",
        #                         "JSONdata": {
        #                             "fees": 0,
        #                             "notes": "Via Crypto",
        #                             "txHash": "0x2e4875185b0f312d8e24b2d26d46bf9877db798b608ad2ff97b2b8bc7d8134e5",
        #                             "last4Digits": null,
        #                             "IBAN": null,
        #                             "alternativeChain": "polygon",
        #                             "referenceId": "657973a9b6eadf0f33d70100",
        #                             "status": "completed",
        #                             "tracked": True
        #                         },
        #                         "amount": 99,
        #                         "timestamp": "2023-12-13T09:04:51.270Z",
        #                         "amountEUR": 91.79310117335974
        #                     },
        #                     {
        #                         "description": "Order 65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c SeqNum 10873722342",
        #                         "JSONdata": {
        #                             "price": "2282.00 ETH/USDC",
        #                             "fees": 0,
        #                             "notes": "Order 3a8c5b4d6c"
        #                         },
        #                         "amount": -4.564,
        #                         "timestamp": "2023-12-14T16:16:50.760Z",
        #                         "amountEUR": -4.150043849187587
        #                     },
        #                     ...
        #                 ]
        #             },
        #             {
        #                 "currency": "ETH",
        #                 "label": "ETH",
        #                 "userId": "65671262d93d9525ac009e36",
        #                 "balance": 0,
        #                 "disabled": False,
        #                 "balanceHistory": [
        #                     {
        #                         "description": "Order 65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c SeqNum 10873722342",
        #                         "JSONdata": {
        #                             "price": "2282.00 ETH/USDC",
        #                             "fees": 0.000002,
        #                             "notes": "Order 3a8c5b4d6c"
        #                         },
        #                         "amount": 0.001998,
        #                         "timestamp": "2023-12-14T16:16:50.761Z",
        #                         "amountEUR": 4.144849415806856
        #                     },
        #                     ...
        #                 ]
        #             },
        #             {
        #                 "currency": "DOGE",
        #                 "label": "DOGE",
        #                 "userId": "65671262d93d9525ac009e36",
        #                 "balance": 0,
        #                 "disabled": False,
        #                 "balanceHistory": [
        #                     {
        #                         "description": "Order 65671262d93d9525ac009e361702905785319b5d9016dc20736034d13ca6a - Swap",
        #                         "JSONdata": {
        #                             "swap": True,
        #                             "subtype": "swap",
        #                             "fees": 0,
        #                             "price": "0.0905469 DOGE/USDC",
        #                             "notes": "Swap 034d13ca6a"
        #                         },
        #                         "amount": 70,
        #                         "timestamp": "2023-12-18T13:23:05.836Z",
        #                         "amountEUR": 5.643627624549227
        #                     }
        #                 ]
        #             },
        #             ...
        #         ]
        #     }
        #
        ledgerByCurrencies = self.safe_value(response, 'list', [])
        ledger = []
        for i in range(0, len(ledgerByCurrencies)):
            currencyLedger = ledgerByCurrencies[i]
            currencyId = self.safe_string(currencyLedger, 'currency')
            balanceHistory = self.safe_value(currencyLedger, 'balanceHistory', [])
            for j in range(0, len(balanceHistory)):
                rawLedgerEntry = balanceHistory[j]
                rawLedgerEntry['currencyId'] = currencyId
                ledger.append(rawLedgerEntry)
        return self.parse_ledger(ledger, currency, since, limit)

    def parse_ledger_entry(self, item: dict, currency: Currency = None):
        datetime = self.safe_string(item, 'timestamp')
        currencyId = self.safe_string(item, 'currencyId')
        item = self.omit(item, 'currencyId')
        currency = self.safe_currency(currencyId, currency)
        description = self.safe_string(item, 'description', '')
        type, referenceId = self.parse_ledger_entry_description(description)
        JSONdata = self.safe_value(item, 'JSONdata', {})
        feeCost = self.safe_string(JSONdata, 'fees')
        fee = {
            'cost': feeCost,
            'currency': None,
        }
        amount = self.safe_string(item, 'amount')
        direction = None
        if amount is not None:
            if Precise.string_lt(amount, '0'):
                direction = 'out'
                amount = Precise.string_abs(amount)
            elif Precise.string_gt(amount, '0'):
                direction = 'in'
        return self.safe_ledger_entry({
            'info': item,
            'id': None,
            'timestamp': self.parse8601(datetime),
            'datetime': datetime,
            'direction': direction,
            'account': None,
            'referenceId': referenceId,
            'referenceAccount': None,
            'type': type,
            'currency': currency,
            'amount': amount,
            'before': None,
            'after': None,
            'status': None,
            'fee': fee,
        }, currency)

    def parse_ledger_entry_description(self, description):
        descriptionArray = []
        if description is not None:
            descriptionArray = description.split(' ')
        type = None
        referenceId = None
        length = len(descriptionArray)
        if length > 1:
            type = self.parse_ledger_entry_type(descriptionArray[0])
            if descriptionArray[1] != '-':
                referenceId = descriptionArray[1]
            else:
                referenceId = self.safe_string(descriptionArray, 2)
        return [type, referenceId]

    def parse_ledger_entry_type(self, type):
        types: dict = {
            'Deposit': 'transaction',
            'Withdraw': 'transaction',
            'Order': 'trade',
        }
        return self.safe_string(types, type, type)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#a4895a1d-3f50-40ae-8231-6962ef06c771
        :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.cost]: the quote quantity that can be used alternative for the amount in market orders
        :param str [params.timeInForce]: "GTC", "IOC", "FOK", "GTD"
        :param number [params.expirationTime]: timestamp in millisecond, for GTD orders only
        :param float [params.triggerPrice]: the price at which a trigger order is triggered at
        :param float [params.stopLossPrice]: *margin only* The price at which a stop loss order is triggered at
        :param float [params.takeProfitPrice]: *margin only* The price at which a take profit order is triggered at
        :param bool [params.margin]: True for creating a margin order
        :param str [params.fillStyle]: fill style of the limit order: "sell" fulfills selling quantity "buy" fulfills buying quantity "base" fulfills base currency quantity "quote" fulfills quote currency quantity
        :param str [params.clientOrderId]: client's comment
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
        }
        request['orderType'] = type
        precisedAmount = None
        if amount is not None:
            precisedAmount = self.amount_to_precision(symbol, amount)
        cost = self.safe_value(params, 'cost')
        params = self.omit(params, 'cost')
        if type == 'limit':
            if (price is None) and (cost is None):
                raise ArgumentsRequired(self.id + ' createOrder() requires a price or params.cost argument for a ' + type + ' order')
            elif (price is not None) and (amount is not None):
                costString = Precise.string_mul(self.number_to_string(price), self.number_to_string(precisedAmount))
                cost = self.parse_to_numeric(costString)
        precisedCost = None
        if cost is not None:
            precisedCost = self.cost_to_precision(symbol, cost)
        if side == 'sell':
            request = self.handle_create_order_side(market['baseId'], market['quoteId'], precisedAmount, precisedCost, request)
        elif side == 'buy':
            request = self.handle_create_order_side(market['quoteId'], market['baseId'], precisedCost, precisedAmount, request)
        timeInForce = self.safe_value(params, 'timeInForce')
        if timeInForce is not None:
            params = self.omit(params, 'timeInForce')
            request['timeInForce'] = self.encode_order_time_in_force(timeInForce)
        stopPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
        if stopPrice is not None:
            params = self.omit(params, ['triggerPrice'])
            request['stopPrice'] = self.price_to_precision(symbol, stopPrice)
        userData = self.safe_value(params, 'userData', {})
        comment = self.safe_string_2(params, 'clientOrderId', 'comment')
        if comment is not None:
            params = self.omit(params, ['clientOrderId'])
            userData['comment'] = comment
        stopLossPrice = self.safe_string(params, 'stopLossPrice')
        if stopLossPrice is not None:
            params = self.omit(params, 'stopLossPrice')
            userData['stopLoss'] = self.price_to_precision(symbol, stopLossPrice)
        takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
        if takeProfitPrice is not None:
            params = self.omit(params, 'takeProfitPrice')
            userData['takeProfit'] = self.price_to_precision(symbol, takeProfitPrice)
        if not self.is_empty(userData):
            request['userData'] = userData
        response = self.privatePostExchangeOrdersCreate(self.extend(request, params))
        #
        #     {
        #         "userID": "65671262d93d9525ac009e36",
        #         "orderID": "65671262d93d9525ac009e36170257448481749b7ee2893bafec2",
        #         "orderType": "market",
        #         "buyingCurrency": "ETH",
        #         "sellingCurrency": "USDC",
        #         "buyingQty": 0.002,
        #         "timeInForce": 4,
        #         "boughtQty": 0.002,
        #         "soldQty": 4.587,
        #         "creationTime": 1702574484829,
        #         "seqNumber": 10874285330,
        #         "firstFillTime": 1702574484831,
        #         "lastFillTime": 1702574484831,
        #         "fills": [
        #             {
        #                 "seqNumber": 10874285329,
        #                 "timestamp": 1702574484831,
        #                 "qty": 0.002,
        #                 "price": 2293.5,
        #                 "side": "buy"
        #             }
        #         ],
        #         "completionTime": 1702574484831,
        #         "takerQty": 0.002
        #     }
        #
        return self.parse_order(response, market)

    def handle_create_order_side(self, sellingCurrency, buyingCurrency, sellingQty, buyingQty, request={}):
        request['sellingCurrency'] = sellingCurrency
        request['buyingCurrency'] = buyingCurrency
        if sellingQty is not None:
            request['sellingQty'] = sellingQty
        if buyingQty is not None:
            request['buyingQty'] = buyingQty
        return request

    def encode_order_time_in_force(self, timeInForce):
        timeInForceTypes: dict = {
            'GTC': 1,
            'IOC': 2,
            'GTD': 3,
            'FOK': 4,
        }
        return self.safe_value(timeInForceTypes, timeInForce, timeInForce)

    def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#eaea86da-16ca-4c56-9f00-5b1cb2ad89f8
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#47f913fb-8cab-49f4-bc78-d980e6ced316
        :param str id: order id
        :param str symbol: not used by coinmetro cancelOrder()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.margin]: True for cancelling a margin order
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {
            'orderID': id,
        }
        marginMode = None
        params, params = self.handle_margin_mode_and_params('cancelOrder', params)
        isMargin = self.safe_bool(params, 'margin', False)
        params = self.omit(params, 'margin')
        response = None
        if isMargin or (marginMode is not None):
            response = self.privatePostExchangeOrdersCloseOrderID(self.extend(request, params))
        else:
            response = self.privatePutExchangeOrdersCancelOrderID(self.extend(request, params))
        #
        #     {
        #         "userID": "65671262d93d9525ac009e36",
        #         "orderID": "65671262d93d9525ac009e3617026635256739c996fe17d7cd5d4",
        #         "orderType": "limit",
        #         "buyingCurrency": "ETH",
        #         "sellingCurrency": "USDC",
        #         "fillStyle": "sell",
        #         "orderPlatform": "trade-v3",
        #         "timeInForce": 1,
        #         "buyingQty": 0.005655,
        #         "sellingQty": 11.31,
        #         "boughtQty": 0,
        #         "soldQty": 0,
        #         "creationTime": 1702663525713,
        #         "seqNumber": 10915220048,
        #         "completionTime": 1702928369053
        #     }
        #
        return self.parse_order(response)

    def close_position(self, symbol: str, side: OrderSide = None, params={}):
        """
        closes an open position
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#47f913fb-8cab-49f4-bc78-d980e6ced316
        :param str symbol: not used by coinmetro closePosition()
        :param str [side]: not used by coinmetro closePosition()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.orderID]: order id
        :param number [params.fraction]: fraction of order to close, between 0 and 1(defaults to 1)
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        orderId = self.safe_string(params, 'orderId')
        if orderId is None:
            raise ArgumentsRequired(self.id + ' closePosition() requires a orderId parameter')
        request: dict = {
            'orderID': orderId,
        }
        response = self.privatePostExchangeOrdersCloseOrderID(self.extend(request, params))
        #
        #     {
        #         "userID": "65671262d93d9525ac009e36",
        #         "orderID": "65671262d93d9525ac009e3617030152811996e5b352556d3d7d8_CL",
        #         "orderType": "market",
        #         "buyingCurrency": "ETH",
        #         "sellingCurrency": "EUR",
        #         "margin": True,
        #         "buyingQty": 0.03,
        #         "timeInForce": 4,
        #         "boughtQty": 0.03,
        #         "soldQty": 59.375,
        #         "creationTime": 1703015488482,
        #         "seqNumber": 10925321179,
        #         "firstFillTime": 1703015488483,
        #         "lastFillTime": 1703015488483,
        #         "fills": [
        #             {
        #                 "seqNumber": 10925321178,
        #                 "timestamp": 1703015488483,
        #                 "qty": 0.03,
        #                 "price": 1979.1666666666667,
        #                 "side": "buy"
        #             }
        #         ],
        #         "completionTime": 1703015488483,
        #         "takerQty": 0.03
        #     }
        #
        return self.parse_order(response)

    def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#518afd7a-4338-439c-a651-d4fdaa964138
        :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>`
        """
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        response = self.privateGetExchangeOrdersActive(params)
        orders = self.parse_orders(response, market, since, limit)
        for i in range(0, len(orders)):
            order = orders[i]
            order['status'] = 'open'
        return orders

    def fetch_canceled_and_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple canceled and closed orders made by the user
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#4d48ae69-8ee2-44d1-a268-71f84e557b7b
        :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
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request: dict = {}
        if since is not None:
            request['since'] = since
        response = self.privateGetExchangeOrdersHistorySince(self.extend(request, params))
        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://documenter.getpostman.com/view/3653795/SVfWN6KS#95bbed87-db1c-47a7-a03e-aa247e91d5a6
        :param int|str id: order id
        :param str symbol: not used by coinmetro 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>`
        """
        self.load_markets()
        request: dict = {
            'orderID': id,
        }
        response = self.privateGetExchangeOrdersStatusOrderID(self.extend(request, params))
        #
        #     {
        #         "_id": "657b4e6d60a954244939ac6f",
        #         "userID": "65671262d93d9525ac009e36",
        #         "orderID": "65671262d93d9525ac009e361702576531985b78465468b9cc544",
        #         "orderType": "market",
        #         "buyingCurrency": "ETH",
        #         "sellingCurrency": "USDC",
        #         "buyingQty": 0.004,
        #         "timeInForce": 4,
        #         "boughtQty": 0.004,
        #         "soldQty": 9.236,
        #         "creationTime": 1702576531995,
        #         "seqNumber": 10874644062,
        #         "firstFillTime": 1702576531995,
        #         "lastFillTime": 1702576531995,
        #         "fills": [
        #             {
        #                 "_id": "657b4e6d60a954244939ac70",
        #                 "seqNumber": 10874644061,
        #                 "timestamp": 1702576531995,
        #                 "qty": 0.004,
        #                 "price": 2309,
        #                 "side": "buy"
        #             }
        #         ],
        #         "completionTime": 1702576531995,
        #         "takerQty": 0.004,
        #         "fees": 0.000004,
        #         "isAncillary": False,
        #         "margin": False,
        #         "trade": False,
        #         "canceled": False
        #     }
        #
        return self.parse_order(response)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # createOrder market
        #     {
        #         "userID": "65671262d93d9525ac009e36",
        #         "orderID": "65671262d93d9525ac009e36170257448481749b7ee2893bafec2",
        #         "orderType": "market",
        #         "buyingCurrency": "ETH",
        #         "sellingCurrency": "USDC",
        #         "buyingQty": 0.002,
        #         "timeInForce": 4,
        #         "boughtQty": 0.002,
        #         "soldQty": 4.587,
        #         "creationTime": 1702574484829,
        #         "seqNumber": 10874285330,
        #         "firstFillTime": 1702574484831,
        #         "lastFillTime": 1702574484831,
        #         "fills": [
        #             {
        #                 "seqNumber": 10874285329,
        #                 "timestamp": 1702574484831,
        #                 "qty": 0.002,
        #                 "price": 2293.5,
        #                 "side": "buy"
        #             }
        #         ],
        #         "completionTime": 1702574484831,
        #         "takerQty": 0.002
        #     }
        #
        # createOrder limit
        #     {
        #         "userID": "65671262d93d9525ac009e36",
        #         "orderID": "65671262d93d9525ac009e3617026635256739c996fe17d7cd5d4",
        #         "orderType": "limit",
        #         "buyingCurrency": "ETH",
        #         "sellingCurrency": "USDC",
        #         "fillStyle": "sell",
        #         "orderPlatform": "trade-v3",
        #         "timeInForce": 1,
        #         "buyingQty": 0.005655,
        #         "sellingQty": 11.31,
        #         "boughtQty": 0,
        #         "soldQty": 0,
        #         "creationTime": 1702663525713,
        #         "seqNumber": 10885528683,
        #         "fees": 0,
        #         "fills": [],
        #         "isAncillary": False,
        #         "margin": False,
        #         "trade": False
        #     }
        #
        # fetchOrders market
        #     {
        #         "userID": "65671262d93d9525ac009e36",
        #         "orderID": "65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c",
        #         "orderType": "market",
        #         "buyingCurrency": "ETH",
        #         "sellingCurrency": "USDC",
        #         "buyingQty": 0.002,
        #         "timeInForce": 4,
        #         "boughtQty": 0.002,
        #         "soldQty": 4.564,
        #         "creationTime": 1702570610746,
        #         "seqNumber": 10873722344,
        #         "firstFillTime": 1702570610747,
        #         "lastFillTime": 1702570610747,
        #         "fills": [
        #             {
        #                 "_id": "657b31d360a9542449381bdc",
        #                 "seqNumber": 10873722343,
        #                 "timestamp": 1702570610747,
        #                 "qty": 0.002,
        #                 "price": 2282,
        #                 "side": "buy"
        #             }
        #         ],
        #         "completionTime": 1702570610747,
        #         "takerQty": 0.002,
        #         "fees": 0.000002,
        #         "isAncillary": False,
        #         "margin": False,
        #         "trade": False,
        #         "canceled": False,
        #         "__v": 0
        #     }
        #
        # fetchOrders margin
        #     {
        #         "userData": {
        #             "takeProfit": 1700,
        #             "stopLoss": 2100
        #         },
        #         "_id": "658201d060a95424499394a2",
        #         "seqNumber": 10925300213,
        #         "orderType": "limit",
        #         "buyingCurrency": "EUR",
        #         "sellingCurrency": "ETH",
        #         "userID": "65671262d93d9525ac009e36",
        #         "closedQty": 0.03,
        #         "sellingQty": 0.03,
        #         "buyingQty": 58.8,
        #         "creationTime": 1703015281205,
        #         "margin": True,
        #         "timeInForce": 1,
        #         "boughtQty": 59.31,
        #         "orderID": "65671262d93d9525ac009e3617030152811996e5b352556d3d7d8",
        #         "lastFillTime": 1703015281206,
        #         "soldQty": 0.03,
        #         "closedTime": 1703015488488,
        #         "closedVal": 59.375,
        #         "trade": True,
        #         "takerQty": 59.31,
        #         "firstFillTime": 1703015281206,
        #         "completionTime": 1703015281206,
        #         "fills": [
        #             {
        #                 "_id": "658201d060a95424499394a3",
        #                 "seqNumber": 10925300212,
        #                 "side": "sell",
        #                 "price": 1977,
        #                 "qty": 0.03,
        #                 "timestamp": 1703015281206
        #             },
        #             {
        #                 "_id": "658201d060a95424499394a4",
        #                 "seqNumber": 10925321178,
        #                 "timestamp": 1703015488483,
        #                 "qty": 0.03,
        #                 "price": 1979.1666666666667,
        #                 "side": "buy"
        #             }
        #         ],
        #         "fees": 0.11875000200000001,
        #         "settledQtys": {
        #             "ETH": -0.000092842104710025
        #         },
        #         "isAncillary": False,
        #         "canceled": False
        #     }
        #
        # fetchOrder
        #     {
        #         "_id": "657b4e6d60a954244939ac6f",
        #         "userID": "65671262d93d9525ac009e36",
        #         "orderID": "65671262d93d9525ac009e361702576531985b78465468b9cc544",
        #         "orderType": "market",
        #         "buyingCurrency": "ETH",
        #         "sellingCurrency": "USDC",
        #         "buyingQty": 0.004,
        #         "timeInForce": 4,
        #         "boughtQty": 0.004,
        #         "soldQty": 9.236,
        #         "creationTime": 1702576531995,
        #         "seqNumber": 10874644062,
        #         "firstFillTime": 1702576531995,
        #         "lastFillTime": 1702576531995,
        #         "fills": [
        #             {
        #                 "_id": "657b4e6d60a954244939ac70",
        #                 "seqNumber": 10874644061,
        #                 "timestamp": 1702576531995,
        #                 "qty": 0.004,
        #                 "price": 2309,
        #                 "side": "buy"
        #             }
        #         ],
        #         "completionTime": 1702576531995,
        #         "takerQty": 0.004,
        #         "fees": 0.000004,
        #         "isAncillary": False,
        #         "margin": False,
        #         "trade": False,
        #         "canceled": False
        #     }
        #
        timestamp = self.safe_integer(order, 'creationTime')
        isCanceled = self.safe_value(order, 'canceled')
        status = None
        if isCanceled is True:
            if timestamp is None:
                timestamp = self.safe_integer(order, 'completionTime')  # market orders with bad price gain IOC - we mark them as 'rejected'?
                status = 'rejected'  # these orders don't have the 'creationTime` param and have 'canceled': True
            else:
                status = 'canceled'
        else:
            status = self.safe_string(order, 'status')
            order = self.omit(order, 'status')  # we mark orders from fetchOpenOrders with param 'status': 'open'
        type = self.safe_string(order, 'orderType')
        buyingQty = self.safe_string(order, 'buyingQty')
        sellingQty = self.safe_string(order, 'sellingQty')
        boughtQty = self.safe_string(order, 'boughtQty')
        soldQty = self.safe_string(order, 'soldQty')
        if type == 'market':
            if (buyingQty is None) and (boughtQty is not None) and (boughtQty != '0'):
                buyingQty = boughtQty
            if (sellingQty is None) and (soldQty is not None) and (soldQty != '0'):
                sellingQty = soldQty
        buyingCurrencyId = self.safe_string(order, 'buyingCurrency', '')
        sellingCurrencyId = self.safe_string(order, 'sellingCurrency', '')
        byuingIdPlusSellingId = buyingCurrencyId + sellingCurrencyId
        sellingIdPlusBuyingId = sellingCurrencyId + buyingCurrencyId
        side = None
        marketId = None
        baseAmount = buyingQty
        quoteAmount = buyingQty
        filled = None
        cost = None
        feeInBaseOrQuote = None
        marketsById = self.index_by(self.markets, 'id')
        if self.safe_value(marketsById, byuingIdPlusSellingId) is not None:
            side = 'buy'
            marketId = byuingIdPlusSellingId
            quoteAmount = sellingQty
            filled = boughtQty
            cost = soldQty
            feeInBaseOrQuote = 'base'
        elif self.safe_value(marketsById, sellingIdPlusBuyingId) is not None:
            side = 'sell'
            marketId = sellingIdPlusBuyingId
            baseAmount = sellingQty
            filled = soldQty
            cost = boughtQty
            feeInBaseOrQuote = 'quote'
        price = None
        if (baseAmount is not None) and (quoteAmount is not None):
            price = Precise.string_div(quoteAmount, baseAmount)
        market = self.safe_market(marketId, market)
        fee = None
        feeCost = self.safe_string(order, 'fees')
        if (feeCost is not None) and (feeInBaseOrQuote is not None):
            fee = {
                'currency': market[feeInBaseOrQuote],
                'cost': feeCost,
                'rate': None,
            }
        trades = self.safe_value(order, 'fills', [])
        userData = self.safe_value(order, 'userData', {})
        triggerPrice = self.safe_string(order, 'stopPrice')
        clientOrderId = self.safe_string(userData, 'comment')
        takeProfitPrice = self.safe_string(userData, 'takeProfit')
        stopLossPrice = self.safe_string(userData, 'stopLoss')
        return self.safe_order({
            'id': self.safe_string(order, 'orderID'),
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': self.safe_integer(order, 'lastFillTime'),
            'status': status,
            'symbol': market['symbol'],
            'type': type,
            'timeInForce': self.parse_order_time_in_force(self.safe_integer(order, 'timeInForce')),
            'side': side,
            'price': price,
            'triggerPrice': triggerPrice,
            'takeProfitPrice': takeProfitPrice,
            'stopLossPrice': stopLossPrice,
            'average': None,
            'amount': baseAmount,
            'cost': cost,
            'filled': filled,
            'remaining': None,
            'fee': fee,
            'fees': None,
            'trades': trades,
            'info': order,
        }, market)

    def parse_order_time_in_force(self, timeInForce):
        timeInForceTypes = [
            None,
            'GTC',
            'IOC',
            'GTD',
            'FOK',
        ]
        return self.safe_value(timeInForceTypes, timeInForce, timeInForce)

    def borrow_cross_margin(self, code: str, amount: float, params={}):
        """
        create a loan to borrow margin
        :see: https://documenter.getpostman.com/view/3653795/SVfWN6KS#5b90b3b9-e5db-4d07-ac9d-d680a06fd110
        :param str code: unified currency code of the currency to borrow
        :param float amount: the amount to borrow
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `margin loan structure <https://docs.ccxt_zagrava.com/#/?id=margin-loan-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        currencyId = currency['id']
        request: dict = {}
        request[currencyId] = self.currency_to_precision(code, amount)
        response = self.privatePutUsersMarginCollateral(self.extend(request, params))
        #
        #     {"message": "OK"}
        #
        result = self.safe_value(response, 'result', {})
        transaction = self.parse_margin_loan(result, currency)
        return self.extend(transaction, {
            'amount': amount,
        })

    def parse_margin_loan(self, info, currency: Currency = None):
        currencyId = self.safe_string(info, 'coin')
        return {
            'id': None,
            'currency': self.safe_currency_code(currencyId, currency),
            'amount': None,
            'symbol': None,
            'timestamp': None,
            'datetime': None,
            'info': info,
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        request = self.omit(params, self.extract_params(path))
        endpoint = '/' + self.implode_params(path, params)
        url = self.urls['api'][api] + endpoint
        query = self.urlencode(request)
        if headers is None:
            headers = {}
        headers['ccxt_zagrava'] = 'true'
        if api == 'private':
            if (self.uid is None) and (self.apiKey is not None):
                self.uid = self.apiKey
            if (self.token is None) and (self.secret is not None):
                self.token = self.secret
            if url == 'https://api.coinmetro.com/jwt':  # handle with headers for login endpoint
                headers['X-Device-Id'] = 'bypass'
                if self.twofa is not None:
                    headers['X-OTP'] = self.twofa
            elif url == 'https://api.coinmetro.com/jwtDevice':  # handle with headers for long lived token login endpoint
                headers['X-Device-Id'] = self.uid
                if self.twofa is not None:
                    headers['X-OTP'] = self.twofa
            else:
                headers['Authorization'] = 'Bearer ' + self.token
                if not url.startswith('https://api.coinmetro.com/open'):  # if not sandbox endpoint
                    self.check_required_credentials()
                    headers['X-Device-Id'] = self.uid
            if (method == 'POST') or (method == 'PUT'):
                headers['Content-Type'] = 'application/x-www-form-urlencoded'
                body = self.urlencode(request)
        elif len(query) != 0:
            url += '?' + query
        while(url.endswith('/')):
            url = url[0:len(url) - 1]
        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 response is None:
            return None
        if (code != 200) and (code != 201) and (code != 202):
            feedback = self.id + ' ' + body
            message = self.safe_string(response, 'message')
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            raise ExchangeError(feedback)
        return None
