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

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

from ccxt.base.exchange import Exchange
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import InvalidNonce
from ccxt.base.errors import RequestTimeout
from ccxt.base.decimal_to_precision import TICK_SIZE


class crex24(Exchange):

    def describe(self):
        return self.deep_extend(super(crex24, self).describe(), {
            'id': 'crex24',
            'name': 'CREX24',
            'countries': ['EE'],  # Estonia
            'rateLimit': 500,
            'version': 'v2',
            # new metainfo interface
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'cancelOrders': True,
                'createOrder': True,
                'createReduceOnlyOrder': False,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchBidsAsks': True,
                'fetchBorrowRate': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchBorrowRates': False,
                'fetchBorrowRatesPerSymbol': False,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchLeverage': False,
                'fetchLeverageTiers': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchOrderTrades': True,
                'fetchPosition': False,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': True,
                'fetchTransactionFees': True,
                'fetchTransactions': True,
                'fetchWithdrawals': True,
                'reduceMargin': False,
                'setLeverage': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1m',
                '3m': '3m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '4h': '4h',
                '1d': '1d',
                '1w': '1w',
                '1M': '1mo',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/47813922-6f12cc00-dd5d-11e8-97c6-70f957712d47.jpg',
                'api': {
                    'rest': 'https://api.crex24.com',
                },
                'www': 'https://crex24.com',
                'referral': 'https://crex24.com/?refid=slxsjsjtil8xexl9hksr',
                'doc': 'https://docs.crex24.com/trade-api/v2',
                'fees': 'https://crex24.com/fees',
            },
            'api': {
                'public': {
                    'get': [
                        'currencies',
                        'instruments',
                        'tickers',
                        'recentTrades',
                        'orderBook',
                        'ohlcv',
                        'tradingFeeSchedules',
                        'withdrawalFees',
                        'currencyTransport',
                        'currenciesWithdrawalFees',
                    ],
                },
                'trading': {
                    'get': [
                        'orderStatus',
                        'orderTrades',
                        'activeOrders',
                        'orderHistory',
                        'tradeHistory',
                        'tradingFee',
                        'tradeFee',  # The support of self method has been dropped on February 18, 2020. Please, use tradingFee method instead. https://docs.crex24.com/trade-api/v2/#trade-fee-and-rebate-discontinued
                    ],
                    'post': [
                        'placeOrder',
                        'modifyOrder',
                        'cancelOrdersById',
                        'cancelOrdersByInstrument',
                        'cancelAllOrders',
                    ],
                },
                'account': {
                    'get': [
                        'balance',
                        'depositAddress',
                        'moneyTransfers',
                        'moneyTransferStatus',
                        'previewWithdrawal',
                    ],
                    'post': [
                        'withdraw',
                    ],
                },
            },
            'precisionMode': TICK_SIZE,
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': 0.001,
                    'maker': -0.0001,
                },
                # should be deleted, these are outdated and inaccurate
                'funding': {
                    'tierBased': False,
                    'percentage': False,
                    'withdraw': {},
                    'deposit': {},
                },
            },
            'commonCurrencies': {
                'ACM': 'Actinium',
                'BCC': 'BCH',
                'BIT': 'BitMoney',
                'BULL': 'BuySell',
                'CLC': 'CaluraCoin',
                'CREDIT': 'TerraCredit',
                'DMS': 'Documentchain',  # conflict with Dragon Mainland Shards
                'EGG': 'NestEGG Coin',
                'EPS': 'Epanus',  # conflict with EPS Ellipsis https://github.com/ccxt/ccxt/issues/8909
                'FUND': 'FUNDChains',
                'GHOST': 'GHOSTPRISM',
                'GM': 'GM Holding',
                'GMT': 'GMT Token',
                'GTC': 'GastroCoin',  # conflict with Gitcoin and Game.com
                'IQ': 'IQ.Cash',
                'ONE': 'One Hundred Coin',
                'PUT': 'PutinCoin',
                'SBTC': 'SBTCT',  # SiamBitcoin
                'SPH': 'SapphireCoin',
                'SUPER': 'SuperCoin',
                'UNI': 'Universe',
                'YOYO': 'YOYOW',
            },
            # exchange-specific options
            'options': {
                'networks': {
                    'ETH': 'ERC20',
                    'TRX': 'TRC20',
                    'BSC': 'BEP20',
                },
                'fetchOrdersMethod': 'tradingGetOrderHistory',  # or 'tradingGetActiveOrders'
                'fetchClosedOrdersMethod': 'tradingGetOrderHistory',  # or 'tradingGetActiveOrders'
                'fetchTickersMethod': 'publicGetTicker24hr',
                'defaultTimeInForce': 'GTC',  # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel
                'hasAlreadyAuthenticatedSuccessfully': False,
                'warnOnFetchOpenOrdersWithoutSymbol': True,
                'parseOrderToPrecision': False,  # force amounts and costs in parseOrder to precision
                'newOrderRespType': 'RESULT',  # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
                'fetchTradingFees': {
                    'method': 'fetchPrivateTradingFees',  # or 'fetchPublicTradingFees'
                },
            },
            'exceptions': {
                'exact': {
                    "Parameter 'filter' contains invalid value.": BadRequest,  # eslint-disable-quotes
                    "Mandatory parameter 'instrument' is missing.": BadRequest,  # eslint-disable-quotes
                    "The value of parameter 'till' must be greater than or equal to the value of parameter 'from'.": BadRequest,  # eslint-disable-quotes
                    'Failed to verify request signature.': AuthenticationError,  # eslint-disable-quotes
                    "Nonce error. Make sure that the value passed in the 'X-CREX24-API-NONCE' header is greater in each consecutive request than in the previous one for the corresponding API-Key provided in 'X-CREX24-API-KEY' header.": InvalidNonce,
                    'Market orders are not supported by the instrument currently.': InvalidOrder,
                    "Parameter 'instrument' contains invalid value.": BadSymbol,
                    "Trading has been disabled for the account until the verification is passed. To initiate the verification process, please log into your account at crex24.com and proceed to 'My account' -> 'Verification'.": AccountSuspended,  # {"errorDescription":"Trading has been disabled for the account until the verification is passed. To initiate the verification process, please log into your account at crex24.com and proceed to 'My account' -> 'Verification'."}
                },
                'broad': {
                    'try again later': ExchangeNotAvailable,  # {"errorDescription":"Failed to process the request. Please, try again later."}
                    'API Key': AuthenticationError,  # "API Key '9edc48de-d5b0-4248-8e7e-f59ffcd1c7f1' doesn't exist."
                    'Insufficient funds': InsufficientFunds,  # "Insufficient funds: new order requires 10 ETH which is more than the available balance."
                    'has been delisted.': BadSymbol,  # {"errorDescription":"Instrument '$PAC-BTC' has been delisted."}
                    'is currently suspended.': BadSymbol,  # {"errorDescription":"Trading in BITG-BTC is currently suspended."}
                    'Mandatory parameter': BadRequest,  # {"errorDescription":"Mandatory parameter 'feeCurrency' is missing."}
                    'can not trade': AccountSuspended,  # {"errorDescription":"User 123456 can not trade"}
                },
            },
        })

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

    def fetch_markets(self, params={}):
        """
        retrieves data on all markets for crex24
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [dict]: an array of objects representing market data
        """
        response = self.publicGetInstruments(params)
        #
        #         [{
        #             "symbol": "$PAC-BTC",
        #             "baseCurrency": "$PAC",
        #             "quoteCurrency": "BTC",
        #             "feeCurrency": "BTC",
        #             "feeSchedule": "OriginalSchedule",
        #             "tickSize": 0.00000001,
        #             "minPrice": 0.00000001,
        #             "maxPrice": 10000000000.0,
        #             "volumeIncrement": 0.00000001,
        #             "minVolume": 1.0,
        #             "maxVolume": 1000000000.0,
        #             "minQuoteVolume": 0.000000000000001,
        #             "maxQuoteVolume": 100000000000.0,
        #             "supportedOrderTypes": [
        #               "limit"
        #             ],
        #             "state": "delisted"
        #           },
        #           {
        #             "symbol": "1INCH-USDT",
        #             "baseCurrency": "1INCH",
        #             "quoteCurrency": "USDT",
        #             "feeCurrency": "USDT",
        #             "feeSchedule": "FeeSchedule10",
        #             "tickSize": 0.0001,
        #             "minPrice": 0.0001,
        #             "maxPrice": 10000000000.0,
        #             "volumeIncrement": 0.00000001,
        #             "minVolume": 0.01,
        #             "maxVolume": 1000000000.0,
        #             "minQuoteVolume": 0.000000000000001,
        #             "maxQuoteVolume": 100000000000.0,
        #             "supportedOrderTypes": [
        #               "limit"
        #             ],
        #             "state": "active"
        #           },]
        #
        response2 = self.publicGetTradingFeeSchedules(params)
        #
        #     [
        #         {
        #             "name": "FeeSchedule05",
        #             "feeRates": [
        #                 {
        #                     "volumeThreshold": 0.0,
        #                     "maker": 0.0005,
        #                     "taker": 0.0005
        #                 },
        #                 {
        #                     "volumeThreshold": 5.0,
        #                     "maker": 0.0004,
        #                     "taker": 0.0004
        #                 },
        #                 {
        #                     "volumeThreshold": 15.0,
        #                     "maker": 0.0003,
        #                     "taker": 0.0003
        #                 },
        #                 {
        #                     "volumeThreshold": 30.0,
        #                     "maker": 0.0002,
        #                     "taker": 0.0002
        #                 },
        #                 {
        #                     "volumeThreshold": 50.0,
        #                     "maker": 0.0001,
        #                     "taker": 0.0001
        #                 }
        #             ]
        #         },
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            market = response[i]
            id = self.safe_string(market, 'symbol')
            baseId = self.safe_string(market, 'baseCurrency')
            quoteId = self.safe_string(market, 'quoteCurrency')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            maker = None
            taker = None
            feeSchedule = self.safe_string(market, 'feeSchedule')
            for j in range(0, len(response2)):
                feeScheduleName = self.safe_string(response2[j], 'name')
                if feeScheduleName == feeSchedule:
                    feeRates = self.safe_value(response2[j], 'feeRates', [])
                    for k in range(0, len(feeRates)):
                        volumeThreshold = self.safe_number(feeRates[k], 'volumeThreshold')
                        if volumeThreshold == 0:
                            maker = self.safe_number(feeRates[k], 'maker')
                            taker = self.safe_number(feeRates[k], 'taker')
                            break
                    break
            state = self.safe_string(market, 'state')
            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': (state == 'active'),
                'contract': False,
                'linear': None,
                'inverse': None,
                'taker': taker,
                'maker': maker,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': self.safe_number(market, 'volumeIncrement'),
                    'price': self.safe_number(market, 'tickSize'),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': self.safe_number(market, 'minVolume'),
                        'max': self.safe_number(market, 'maxVolume'),
                    },
                    'price': {
                        'min': self.safe_number(market, 'minPrice'),
                        'max': self.safe_number(market, 'maxPrice'),
                    },
                    'cost': {
                        'min': self.safe_number(market, 'minQuoteVolume'),
                        'max': self.safe_number(market, 'maxQuoteVolume'),
                    },
                },
                'info': market,
            })
        return result

    def fetch_currencies(self, params={}):
        """
        fetches all available currencies on an exchange
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = self.publicGetCurrencies(params)
        #
        #     [{                  symbol: "$PAC",
        #                             name: "PACCoin",
        #                           isFiat:  False,
        #                  depositsAllowed:  True,
        #         depositConfirmationCount:  8,
        #                       minDeposit:  0,
        #               withdrawalsAllowed:  True,
        #              withdrawalPrecision:  8,
        #                    minWithdrawal:  4,
        #                    maxWithdrawal:  1000000000,
        #                flatWithdrawalFee:  2,
        #                       isDelisted:  False       },
        #       {                  symbol: "ZZC",
        #                             name: "Zozo",
        #                           isFiat:  False,
        #                  depositsAllowed:  False,
        #         depositConfirmationCount:  8,
        #                       minDeposit:  0,
        #               withdrawalsAllowed:  False,
        #              withdrawalPrecision:  8,
        #                    minWithdrawal:  0.2,
        #                    maxWithdrawal:  1000000000,
        #                flatWithdrawalFee:  0.1,
        #                       isDelisted:  False       }]
        #
        result = {}
        for i in range(0, len(response)):
            currency = response[i]
            id = self.safe_string(currency, 'symbol')
            code = self.safe_currency_code(id)
            precision = self.parse_number(self.parse_precision(self.safe_string(currency, 'withdrawalPrecision')))
            address = self.safe_value(currency, 'BaseAddress')
            deposit = self.safe_value(currency, 'depositsAllowed')
            withdraw = self.safe_value(currency, 'withdrawalsAllowed')
            delisted = self.safe_value(currency, 'isDelisted')
            active = deposit and withdraw and not delisted
            fiat = self.safe_value(currency, 'isFiat')
            type = 'fiat' if fiat else 'crypto'
            result[code] = {
                'id': id,
                'code': code,
                'address': address,
                'info': currency,
                'type': type,
                'name': self.safe_string(currency, 'name'),
                'active': active,
                'deposit': deposit,
                'withdraw': withdraw,
                'fee': self.safe_number(currency, 'flatWithdrawalFee'),  # todo: redesign
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': precision,
                        'max': None,
                    },
                    'deposit': {
                        'min': self.safe_number(currency, 'minDeposit'),
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_number(currency, 'minWithdrawal'),
                        'max': self.safe_number(currency, 'maxWithdrawal'),
                    },
                },
            }
        return result

    def fetch_transaction_fees(self, codes=None, params={}):
        """
        fetch transaction fees
        :param [str]|None codes: not used by crex24 fetchTransactionFees
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: a list of `transaction fees structures <https://docs.ccxt.com/en/latest/manual.html#fee-structure>`
        """
        self.load_markets()
        response = self.publicGetCurrenciesWithdrawalFees(params)
        #
        #     [
        #         {
        #             currency: '1INCH',
        #             fees: [
        #                 {feeCurrency: 'BTC', amount: 0.00032},
        #                 {feeCurrency: 'ETH', amount: 0.0054},
        #                 {feeCurrency: 'DOGE', amount: 63.06669},
        #                 {feeCurrency: 'LTC', amount: 0.0912},
        #                 {feeCurrency: 'BCH', amount: 0.02364},
        #                 {feeCurrency: 'USDT', amount: 12.717},
        #                 {feeCurrency: 'USDC', amount: 12.7367},
        #                 {feeCurrency: 'TRX', amount: 205.99108},
        #                 {feeCurrency: 'EOS', amount: 3.30141}
        #             ]
        #         }
        #     ]
        #
        withdrawFees = {}
        for i in range(0, len(response)):
            entry = response[i]
            currencyId = self.safe_string(entry, 'currency')
            code = self.safe_currency_code(currencyId)
            networkList = self.safe_value(entry, 'fees')
            withdrawFees[code] = {}
            for j in range(0, len(networkList)):
                networkEntry = networkList[j]
                networkId = self.safe_string(networkEntry, 'feeCurrency')
                networkCode = self.safe_currency_code(networkId)
                fee = self.safe_number(networkEntry, 'amount')
                withdrawFees[code][networkCode] = fee
        return {
            'withdraw': withdrawFees,
            'deposit': {},
            'info': response,
        }

    def parse_balance(self, response):
        result = {'info': response}
        for i in range(0, len(response)):
            balance = response[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(balance, 'available')
            account['used'] = self.safe_string(balance, 'reserved')
            result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        self.load_markets()
        request = {
            # 'currency': 'ETH',  # comma-separated list of currency ids
            # 'nonZeroOnly': 'false',  # True by default
        }
        response = self.accountGetBalance(self.extend(request, params))
        #
        #     [
        #         {
        #           "currency": "ETH",
        #           "available": 0.0,
        #           "reserved": 0.0
        #         }
        #     ]
        #
        return self.parse_balance(response)

    def fetch_order_book(self, symbol, limit=None, params={}):
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'instrument': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default = maximum = 100
        response = self.publicGetOrderBook(self.extend(request, params))
        #
        #     { buyLevels: [{price: 0.03099, volume: 0.00610063},
        #                     {price: 0.03097, volume: 1.33455158},
        #                     {price: 0.03096, volume: 0.0830889},
        #                     {price: 0.03095, volume: 0.0820356},
        #                     {price: 0.03093, volume: 0.5499419},
        #                     {price: 0.03092, volume: 0.23317494},
        #                     {price: 0.03091, volume: 0.62105322},
        #                     {price: 0.00620041, volume: 0.003}    ],
        #       sellLevels: [{price: 0.03117, volume: 5.47492315},
        #                     {price: 0.03118, volume: 1.97744139},
        #                     {price: 0.03119, volume: 0.012},
        #                     {price: 0.03121, volume: 0.741242},
        #                     {price: 0.03122, volume: 0.96178089},
        #                     {price: 0.03123, volume: 0.152326},
        #                     {price: 0.03124, volume: 2.63462933},
        #                     {price: 0.069, volume: 0.004}            ]}
        #
        return self.parse_order_book(response, market['symbol'], None, 'buyLevels', 'sellLevels', 'price', 'volume')

    def parse_ticker(self, ticker, market=None):
        #
        #       {   instrument: "ZZC-USD",
        #                  last:  0.065,
        #         percentChange:  0,
        #                   low:  0.065,
        #                  high:  0.065,
        #            baseVolume:  0,
        #           quoteVolume:  0,
        #           volumeInBtc:  0,
        #           volumeInUsd:  0,
        #                   ask:  0.5,
        #                   bid:  0.0007,
        #             timestamp: "2018-10-31T09:21:25Z"}   ]
        #
        timestamp = self.parse8601(self.safe_string(ticker, 'timestamp'))
        marketId = self.safe_string(ticker, 'instrument')
        symbol = self.safe_symbol(marketId, market, '-')
        last = self.safe_string(ticker, 'last')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': self.safe_string(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'ask'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,  # previous day close
            'change': None,
            'percentage': self.safe_string(ticker, 'percentChange'),
            'average': None,
            'baseVolume': self.safe_string(ticker, 'baseVolume'),
            'quoteVolume': self.safe_string(ticker, 'quoteVolume'),
            'info': ticker,
        }, market)

    def fetch_ticker(self, symbol, params={}):
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'instrument': market['id'],
        }
        response = self.publicGetTickers(self.extend(request, params))
        #
        #     [{   instrument: "$PAC-BTC",
        #                  last:  3.3e-7,
        #         percentChange:  3.125,
        #                   low:  2.7e-7,
        #                  high:  3.3e-7,
        #            baseVolume:  191700.79823187,
        #           quoteVolume:  0.0587930939346704,
        #           volumeInBtc:  0.0587930939346704,
        #           volumeInUsd:  376.2006339435353,
        #                   ask:  3.3e-7,
        #                   bid:  3.1e-7,
        #             timestamp: "2018-10-31T09:21:25Z"}   ]
        #
        numTickers = len(response)
        if numTickers < 1:
            raise ExchangeError(self.id + ' fetchTicker() could not load quotes for symbol ' + symbol)
        return self.parse_ticker(response[0], market)

    def fetch_tickers(self, symbols=None, params={}):
        """
        fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
        :param [str]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: an array of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        request = {}
        if symbols is not None:
            ids = self.market_ids(symbols)
            request['instrument'] = ','.join(ids)
        response = self.publicGetTickers(self.extend(request, params))
        #
        #     [{   instrument: "$PAC-BTC",
        #                  last:  3.3e-7,
        #         percentChange:  3.125,
        #                   low:  2.7e-7,
        #                  high:  3.3e-7,
        #            baseVolume:  191700.79823187,
        #           quoteVolume:  0.0587930939346704,
        #           volumeInBtc:  0.0587930939346704,
        #           volumeInUsd:  376.2006339435353,
        #                   ask:  3.3e-7,
        #                   bid:  3.1e-7,
        #             timestamp: "2018-10-31T09:21:25Z"},
        #       {   instrument: "ZZC-USD",
        #                  last:  0.065,
        #         percentChange:  0,
        #                   low:  0.065,
        #                  high:  0.065,
        #            baseVolume:  0,
        #           quoteVolume:  0,
        #           volumeInBtc:  0,
        #           volumeInUsd:  0,
        #                   ask:  0.5,
        #                   bid:  0.0007,
        #             timestamp: "2018-10-31T09:21:25Z"}   ]
        #
        return self.parse_tickers(response, symbols)

    def parse_trade(self, trade, market=None):
        #
        # public fetchTrades
        #
        #       {    price:  0.03105,
        #            volume:  0.11,
        #              side: "sell",
        #         timestamp: "2018-10-31T04:19:35Z"}  ]
        #
        # private fetchMyTrades
        #
        #     {
        #         "id": 3005866,
        #         "orderId": 468533093,
        #         "timestamp": "2018-06-02T16:26:27Z",
        #         "instrument": "BCH-ETH",
        #         "side": "buy",
        #         "price": 1.78882,
        #         "volume": 0.027,
        #         "fee": 0.0000483,
        #         "feeCurrency": "ETH"
        #     }
        #
        timestamp = self.parse8601(self.safe_string(trade, 'timestamp'))
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'volume')
        id = self.safe_string(trade, 'id')
        side = self.safe_string(trade, 'side')
        orderId = self.safe_string(trade, 'orderId')
        marketId = self.safe_string(trade, 'instrument')
        symbol = self.safe_symbol(marketId, market, '-')
        fee = None
        feeCurrencyId = self.safe_string(trade, 'feeCurrency')
        feeCode = self.safe_currency_code(feeCurrencyId)
        feeCostString = self.safe_string(trade, 'fee')
        if feeCostString is not None:
            fee = {
                'cost': feeCostString,
                'currency': feeCode,
            }
        takerOrMaker = None
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': None,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': priceString,
            'cost': None,
            'amount': amountString,
            'fee': fee,
        }, market)

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        """
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'instrument': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # min 1, max 1000, default 100
        response = self.publicGetRecentTrades(self.extend(request, params))
        #
        #     [{    price:  0.03117,
        #            volume:  0.02597403,
        #              side: "buy",
        #         timestamp: "2018-10-31T09:37:46Z"},
        #       {    price:  0.03105,
        #            volume:  0.11,
        #              side: "sell",
        #         timestamp: "2018-10-31T04:19:35Z"}  ]
        #
        return self.parse_trades(response, market, since, limit)

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

    def fetch_public_trading_fees(self, params={}):
        self.load_markets()
        response = self.publicGetTradingFeeSchedules(params)
        #
        #     [
        #         {
        #             name: 'FeeSchedule05',
        #             feeRates: [
        #                 {volumeThreshold: 0, maker: 0.0005, taker: 0.0005},
        #                 {volumeThreshold: 5, maker: 0.0004, taker: 0.0004},
        #                 {volumeThreshold: 15, maker: 0.0003, taker: 0.0003},
        #                 {volumeThreshold: 30, maker: 0.0002, taker: 0.0002},
        #                 {volumeThreshold: 50, maker: 0.0001, taker: 0.0001}
        #             ]
        #         },
        #         {
        #             name: 'OriginalSchedule',
        #             feeRates: [
        #                 {volumeThreshold: 0, maker: -0.0001, taker: 0.001},
        #                 {volumeThreshold: 5, maker: -0.0002, taker: 0.0009},
        #                 {volumeThreshold: 15, maker: -0.0003, taker: 0.0008},
        #                 {volumeThreshold: 30, maker: -0.0004, taker: 0.0007},
        #                 {volumeThreshold: 50, maker: -0.0005, taker: 0.0006}
        #             ]
        #         }
        #     ]
        #
        feeSchedulesByName = self.index_by(response, 'name')
        originalSchedule = self.safe_value(feeSchedulesByName, 'OriginalSchedule', {})
        feeRates = self.safe_value(originalSchedule, 'feeRates', [])
        firstFee = self.safe_value(feeRates, 0, {})
        maker = self.safe_number(firstFee, 'maker')
        taker = self.safe_number(firstFee, 'taker')
        result = {}
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            result[symbol] = {
                'info': response,
                'symbol': symbol,
                'maker': maker,
                'taker': taker,
                'percentage': True,
                'tierBased': True,
            }
        return result

    def fetch_private_trading_fees(self, params={}):
        self.load_markets()
        response = self.tradingGetTradingFee(params)
        #
        #     {
        #         feeRates: [
        #             {schedule: 'FeeSchedule05', maker: 0.0005, taker: 0.0005},
        #             {schedule: 'FeeSchedule08', maker: 0.0008, taker: 0.0008},
        #             {schedule: 'FeeSchedule10', maker: 0.001, taker: 0.001},
        #             {schedule: 'FeeSchedule15', maker: 0.0015, taker: 0.0015},
        #             {schedule: 'FeeSchedule20', maker: 0.002, taker: 0.002},
        #             {schedule: 'FeeSchedule30', maker: 0.003, taker: 0.003},
        #             {schedule: 'FeeSchedule40', maker: 0.004, taker: 0.004},
        #             {schedule: 'FeeSchedule50', maker: 0.005, taker: 0.005},
        #             {schedule: 'OriginalSchedule', maker: -0.0001, taker: 0.001}
        #         ],
        #         tradingVolume: 0,
        #         lastUpdate: '2022-03-16T04:55:02Z'
        #     }
        #
        feeRates = self.safe_value(response, 'feeRates', [])
        feeRatesBySchedule = self.index_by(feeRates, 'schedule')
        originalSchedule = self.safe_value(feeRatesBySchedule, 'OriginalSchedule', {})
        maker = self.safe_number(originalSchedule, 'maker')
        taker = self.safe_number(originalSchedule, 'taker')
        result = {}
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            result[symbol] = {
                'info': response,
                'symbol': symbol,
                'maker': maker,
                'taker': taker,
                'percentage': True,
                'tierBased': True,
            }
        return result

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         timestamp: '2019-09-21T10:36:00Z',
        #         open: 0.02152,
        #         high: 0.02156,
        #         low: 0.02152,
        #         close: 0.02156,
        #         volume: 0.01741259
        #     }
        #
        return [
            self.parse8601(self.safe_string(ohlcv, 'timestamp')),
            self.safe_number(ohlcv, 'open'),
            self.safe_number(ohlcv, 'high'),
            self.safe_number(ohlcv, 'low'),
            self.safe_number(ohlcv, 'close'),
            self.safe_number(ohlcv, 'volume'),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'granularity': self.timeframes[timeframe],
            'instrument': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # Accepted values: 1 - 1000. If the parameter is not specified, the number of results is limited to 100
        response = self.publicGetOhlcv(self.extend(request, params))
        #
        #     [
        #         {
        #             "timestamp": "2020-06-06T17:36:00Z",
        #             "open": 0.025,
        #             "high": 0.025,
        #             "low": 0.02499,
        #             "close": 0.02499,
        #             "volume": 0.00643127
        #         }
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def parse_order_status(self, status):
        statuses = {
            'submitting': 'open',  # A newly created limit order has a status "submitting" until it has been processed.
            # This status changes during the lifetime of an order and can have different values depending on the value of the parameter Time In Force.
            'unfilledActive': 'open',  # order is active, no trades have been made
            'partiallyFilledActive': 'open',  # part of the order has been filled, the other part is active
            'filled': 'closed',  # order has been filled entirely
            'partiallyFilledCancelled': 'canceled',  # part of the order has been filled, the other part has been cancelled either by the trader or by the system(see the value of cancellationReason of an Order for more details on the reason of cancellation)
            'unfilledCancelled': 'canceled',  # order has been cancelled, no trades have taken place(see the value of cancellationReason of an Order for more details on the reason of cancellation)
        }
        return statuses[status] if (status in statuses) else status

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "id": 469594855,
        #         "timestamp": "2018-06-08T16:59:44Z",
        #         "instrument": "BTS-BTC",
        #         "side": "buy",
        #         "type": "limit",
        #         "status": "submitting",
        #         "cancellationReason": null,
        #         "timeInForce": "GTC",
        #         "volume": 4.0,
        #         "price": 0.000025,
        #         "stopPrice": null,
        #         "remainingVolume": 4.0,
        #         "lastUpdate": null,
        #         "parentOrderId": null,
        #         "childOrderId": null
        #     }
        #
        # cancelOrder, cancelOrders, cancelAllOrders
        #
        #  465448358
        #
        status = self.parse_order_status(self.safe_string(order, 'status'))
        marketId = self.safe_string(order, 'instrument')
        symbol = self.safe_symbol(marketId, market, '-')
        timestamp = self.parse8601(self.safe_string(order, 'timestamp'))
        price = self.safe_string(order, 'price')
        amount = self.safe_string(order, 'volume')
        remaining = self.safe_string(order, 'remainingVolume')
        lastTradeTimestamp = self.parse8601(self.safe_string(order, 'lastUpdate'))
        id = self.safe_string(order, 'id', order)  # if order was integer
        type = self.safe_string(order, 'type')
        side = self.safe_string(order, 'side')
        timeInForce = self.safe_string(order, 'timeInForce')
        stopPrice = self.safe_number(order, 'stopPrice')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'amount': amount,
            'cost': None,
            'average': None,
            'filled': None,
            'remaining': remaining,
            'status': status,
            'fee': None,
            'trades': None,
        }, market)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        """
        create a trade order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float|None price: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'instrument': market['id'],
            'volume': self.amount_to_precision(symbol, amount),
            # The value must comply with the list of order types supported by the instrument(see the value of parameter supportedOrderTypes of the Instrument)
            # If the parameter is not specified, the default value "limit" is used
            # More about order types in the corresponding section of documentation
            'type': type,  # 'limit', 'market', 'stopLimit', in fact as of 2018-10-31, only 'limit' orders are supported for all markets
            'side': side,  # 'buy' or 'sell'
            # "GTC" - Good-Til-Cancelled
            # "IOC" - Immediate-Or-Cancel(currently not supported by the exchange API, reserved for future use)
            # "FOK" - Fill-Or-Kill(currently not supported by the exchange API, reserved for future use)
            # 'timeInForce': 'GTC',  # IOC', 'FOK'
            # 'strictValidation': False,  # False - prices will be rounded to meet the requirement, True - execution of the method will be aborted and an error message will be returned
        }
        priceIsRequired = False
        stopPriceIsRequired = False
        if type == 'limit':
            priceIsRequired = True
        elif type == 'stopLimit':
            priceIsRequired = True
            stopPriceIsRequired = True
        if priceIsRequired:
            if price is None:
                raise InvalidOrder(self.id + ' createOrder() requires a price argument for a ' + type + ' order')
            request['price'] = self.price_to_precision(symbol, price)
        if stopPriceIsRequired:
            stopPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
            if stopPrice is None:
                raise InvalidOrder(self.id + ' createOrder() requires a stopPrice extra param for a ' + type + ' order')
            else:
                request['stopPrice'] = self.price_to_precision(symbol, stopPrice)
            params = self.omit(params, ['triggerPrice', 'stopPrice'])
        response = self.tradingPostPlaceOrder(self.extend(request, params))
        #
        #     {
        #         "id": 469594855,
        #         "timestamp": "2018-06-08T16:59:44Z",
        #         "instrument": "BTS-BTC",
        #         "side": "buy",
        #         "type": "limit",
        #         "status": "submitting",
        #         "cancellationReason": null,
        #         "timeInForce": "GTC",
        #         "volume": 4.0,
        #         "price": 0.000025,
        #         "stopPrice": null,
        #         "remainingVolume": 4.0,
        #         "lastUpdate": null,
        #         "parentOrderId": null,
        #         "childOrderId": null
        #     }
        #
        return self.parse_order(response, market)

    def fetch_order(self, id, symbol=None, params={}):
        """
        fetches information on an order made by the user
        :param str|None symbol: unified symbol of the market the order was made in
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        request = {
            'id': id,
        }
        response = self.tradingGetOrderStatus(self.extend(request, params))
        #
        #     [
        #         {
        #           "id": 466747915,
        #           "timestamp": "2018-05-26T06:43:49Z",
        #           "instrument": "UNI-BTC",
        #           "side": "sell",
        #           "type": "limit",
        #           "status": "partiallyFilledActive",
        #           "cancellationReason": null,
        #           "timeInForce": "GTC",
        #           "volume": 5700.0,
        #           "price": 0.000005,
        #           "stopPrice": null,
        #           "remainingVolume": 1.948051948052,
        #           "lastUpdate": null,
        #           "parentOrderId": null,
        #           "childOrderId": null
        #         }
        #     ]
        #
        numOrders = len(response)
        if numOrders < 1:
            raise OrderNotFound(self.id + ' fetchOrder() could not fetch order id ' + id)
        return self.parse_order(response[0])

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches information on multiple orders made by the user
        :param str|None symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        request = {}
        if since is not None:
            request['from'] = self.ymdhms(since, 'T')
        if limit is not None:
            request['limit'] = limit
        if symbol is not None:
            market = self.market(symbol)
            request['instrument'] = market['id']
        method = self.safe_string(self.options, 'fetchOrdersMethod', 'tradingGetOrderHistory')
        response = getattr(self, method)(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": 468535711,
        #             "timestamp": "2018-06-02T16:42:40Z",
        #             "instrument": "BTC-EUR",
        #             "side": "sell",
        #             "type": "limit",
        #             "status": "submitting",
        #             "cancellationReason": null,
        #             "timeInForce": "GTC",
        #             "volume": 0.00770733,
        #             "price": 6724.9,
        #             "stopPrice": null,
        #             "remainingVolume": 0.00770733,
        #             "lastUpdate": "2018-06-02T16:42:40Z",
        #             "parentOrderId": null,
        #             "childOrderId": null
        #         }
        #     ]
        #
        return self.parse_orders(response)

    def fetch_orders_by_ids(self, ids=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            'id': ','.join(ids),
        }
        response = self.tradingGetOrderStatus(self.extend(request, params))
        #
        #     [
        #         {
        #           "id": 466747915,
        #           "timestamp": "2018-05-26T06:43:49Z",
        #           "instrument": "UNI-BTC",
        #           "side": "sell",
        #           "type": "limit",
        #           "status": "partiallyFilledActive",
        #           "cancellationReason": null,
        #           "timeInForce": "GTC",
        #           "volume": 5700.0,
        #           "price": 0.000005,
        #           "stopPrice": null,
        #           "remainingVolume": 1.948051948052,
        #           "lastUpdate": null,
        #           "parentOrderId": null,
        #           "childOrderId": null
        #         }
        #     ]
        #
        return self.parse_orders(response, None, since, limit)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all unfilled currently open orders
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch open orders for
        :param int|None limit: the maximum number of  open orders structures to retrieve
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['instrument'] = market['id']
        response = self.tradingGetActiveOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": 466747915,
        #             "timestamp": "2018-05-26T06:43:49Z",
        #             "instrument": "UNI-BTC",
        #             "side": "sell",
        #             "type": "limit",
        #             "status": "partiallyFilledActive",
        #             "cancellationReason": null,
        #             "timeInForce": "GTC",
        #             "volume": 5700.0,
        #             "price": 0.000005,
        #             "stopPrice": null,
        #             "remainingVolume": 1.948051948052,
        #             "lastUpdate": null,
        #             "parentOrderId": null,
        #             "childOrderId": null
        #         },
        #         {
        #             "id": 466748077,
        #             "timestamp": "2018-05-26T06:45:29Z",
        #             "instrument": "PRJ-BTC",
        #             "side": "sell",
        #             "type": "limit",
        #             "status": "partiallyFilledActive",
        #             "cancellationReason": null,
        #             "timeInForce": "GTC",
        #             "volume": 10000.0,
        #             "price": 0.0000007,
        #             "stopPrice": null,
        #             "remainingVolume": 9975.0,
        #             "lastUpdate": null,
        #             "parentOrderId": null,
        #             "childOrderId": null
        #         },
        #         ...
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches information on multiple closed orders made by the user
        :param str|None symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['instrument'] = market['id']
        if since is not None:
            request['from'] = self.ymdhms(since, 'T')
        if limit is not None:
            request['limit'] = limit  # min 1, max 1000, default 100
        method = self.safe_string(self.options, 'fetchClosedOrdersMethod', 'tradingGetOrderHistory')
        response = getattr(self, method)(self.extend(request, params))
        #     [
        #         {
        #             "id": 468535711,
        #             "timestamp": "2018-06-02T16:42:40Z",
        #             "instrument": "BTC-EUR",
        #             "side": "sell",
        #             "type": "limit",
        #             "status": "submitting",
        #             "cancellationReason": null,
        #             "timeInForce": "GTC",
        #             "volume": 0.00770733,
        #             "price": 6724.9,
        #             "stopPrice": null,
        #             "remainingVolume": 0.00770733,
        #             "lastUpdate": null,
        #             "parentOrderId": null,
        #             "childOrderId": null
        #         },
        #         {
        #             "id": 468535707,
        #             "timestamp": "2018-06-02T16:42:37Z",
        #             "instrument": "BTG-BTC",
        #             "side": "buy",
        #             "type": "limit",
        #             "status": "unfilledActive",
        #             "cancellationReason": null,
        #             "timeInForce": "GTC",
        #             "volume": 0.0173737,
        #             "price": 0.00589027,
        #             "stopPrice": null,
        #             "remainingVolume": 0.0173737,
        #             "lastUpdate": null,
        #             "parentOrderId": null,
        #             "childOrderId": null
        #         },
        #         ...
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def cancel_order(self, id, symbol=None, params={}):
        """
        cancels an open order
        :param str id: order id
        :param str|None symbol: unified symbol of the market the order was made in
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        response = self.cancel_orders([id], symbol, params)
        return self.safe_value(response, 0)

    def cancel_orders(self, ids, symbol=None, params={}):
        """
        cancel multiple orders
        :param [str] ids: order ids
        :param str|None symbol: not used by crex24 cancelOrders()
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: an list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        if not isinstance(ids, list):
            raise ArgumentsRequired(self.id + ' cancelOrders() ids argument should be an array')
        self.load_markets()
        request = {
            'ids': [],
        }
        for i in range(0, len(ids)):
            id = int(ids[i])
            request['ids'].append(id)
        response = self.tradingPostCancelOrdersById(self.extend(request, params))
        #
        #     [
        #         465448358,
        #         468364313
        #     ]
        #
        return self.parse_orders(response)

    def cancel_all_orders(self, symbol=None, params={}):
        """
        cancel all open orders
        :param str|None symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        response = None
        market = None
        if symbol is None:
            response = self.tradingPostCancelAllOrders(params)
            #
            #     [
            #         465448358,
            #         468364313
            #     ]
            #
        else:
            self.load_markets()
            market = self.market(symbol)
            request = {
                'instruments': [market['id']],
            }
            response = self.tradingPostCancelOrdersByInstrument(self.extend(request, params))
            #
            #     [
            #         465441234,
            #         468364321
            #     ]
            #
        return self.parse_orders(response, market, None, None, params)

    def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        """
        fetch all the trades made from a single order
        :param str id: order id
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades to retrieve
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        self.load_markets()
        request = {
            'id': id,
        }
        response = self.tradingGetOrderTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": 3005866,
        #             "orderId": 468533093,
        #             "timestamp": "2018-06-02T16:26:27Z",
        #             "instrument": "BCH-ETH",
        #             "side": "buy",
        #             "price": 1.78882,
        #             "volume": 0.027,
        #             "fee": 0.0000483,
        #             "feeCurrency": "ETH"
        #         },
        #         {
        #             "id": 3005812,
        #             "orderId": 468515771,
        #             "timestamp": "2018-06-02T16:16:05Z",
        #             "instrument": "ETC-BTC",
        #             "side": "sell",
        #             "price": 0.00210958,
        #             "volume": 0.05994006,
        #             "fee": -0.000000063224,
        #             "feeCurrency": "BTC"
        #         },
        #         ...
        #     ]
        #
        return self.parse_trades(response, None, since, limit)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all trades made by the user
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades structures to retrieve
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['instrument'] = market['id']
        if since is not None:
            request['from'] = self.ymdhms(since, 'T')
        if limit is not None:
            request['limit'] = limit  # min 1, max 1000, default 100
        response = self.tradingGetTradeHistory(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": 3005866,
        #             "orderId": 468533093,
        #             "timestamp": "2018-06-02T16:26:27Z",
        #             "instrument": "BCH-ETH",
        #             "side": "buy",
        #             "price": 1.78882,
        #             "volume": 0.027,
        #             "fee": 0.0000483,
        #             "feeCurrency": "ETH"
        #         },
        #         {
        #             "id": 3005812,
        #             "orderId": 468515771,
        #             "timestamp": "2018-06-02T16:16:05Z",
        #             "instrument": "ETC-BTC",
        #             "side": "sell",
        #             "price": 0.00210958,
        #             "volume": 0.05994006,
        #             "fee": -0.000000063224,
        #             "feeCurrency": "BTC"
        #         },
        #         ...
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        """
        fetch history of deposits and withdrawals
        :param str|None code: unified currency code for the currency of the transactions, default is None
        :param int|None since: timestamp in ms of the earliest transaction, default is None
        :param int|None limit: max number of transactions to return, default is None
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: a list of `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        self.load_markets()
        currency = None
        request = {}
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if since is not None:
            request['from'] = self.ymdhms(since, 'T')
        response = self.accountGetMoneyTransfers(self.extend(request, params))
        #
        #     [
        #         {
        #           "id": 756446,
        #           "type": "deposit",
        #           "currency": "ETH",
        #           "address": "0x451d5a1b7519aa75164f440df78c74aac96023fe",
        #           "paymentId": null,
        #           "amount": 0.142,
        #           "fee": null,
        #           "txId": "0x2b49098749840a9482c4894be94f94864b498a1306b6874687a5640cc9871918",
        #           "createdAt": "2018-06-02T19:30:28Z",
        #           "processedAt": "2018-06-02T21:10:41Z",
        #           "confirmationsRequired": 12,
        #           "confirmationCount": 12,
        #           "status": "success",
        #           "errorDescription": null
        #         },
        #         {
        #           "id": 754618,
        #           "type": "deposit",
        #           "currency": "BTC",
        #           "address": "1IgNfmERVcier4IhfGEfutkLfu4AcmeiUC",
        #           "paymentId": null,
        #           "amount": 0.09,
        #           "fee": null,
        #           "txId": "6876541687a9187e987c9187654f7198b9718af974641687b19a87987f91874f",
        #           "createdAt": "2018-06-02T16:19:44Z",
        #           "processedAt": "2018-06-02T16:20:50Z",
        #           "confirmationsRequired": 1,
        #           "confirmationCount": 1,
        #           "status": "success",
        #           "errorDescription": null
        #         },
        #         ...
        #     ]
        #
        return self.parse_transactions(response, currency, since, limit)

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        """
        fetch all deposits made to an account
        :param str|None code: unified currency code
        :param int|None since: the earliest time in ms to fetch deposits for
        :param int|None limit: the maximum number of deposits structures to retrieve
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        request = {
            'type': 'deposit',
        }
        return self.fetch_transactions(code, since, limit, self.extend(request, params))

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        """
        fetch all withdrawals made from an account
        :param str|None code: unified currency code
        :param int|None since: the earliest time in ms to fetch withdrawals for
        :param int|None limit: the maximum number of withdrawals structures to retrieve
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        request = {
            'type': 'withdrawal',
        }
        return self.fetch_transactions(code, since, limit, self.extend(request, params))

    def parse_transaction_status(self, status):
        statuses = {
            'pending': 'pending',  # transfer is in progress
            'success': 'ok',  # completed successfully
            'failed': 'failed',  # aborted at some point(money will be credited back to the account of origin)
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        #     {
        #         "id": 756446,
        #         "type": "deposit",
        #         "currency": "ETH",
        #         "address": "0x451d5a1b7519aa75164f440df78c74aac96023fe",
        #         "paymentId": null,
        #         "amount": 0.142,
        #         "fee": null,
        #         "txId": "0x2b49098749840a9482c4894be94f94864b498a1306b6874687a5640cc9871918",
        #         "createdAt": "2018-06-02T19:30:28Z",
        #         "processedAt": "2018-06-02T21:10:41Z",
        #         "confirmationsRequired": 12,
        #         "confirmationCount": 12,
        #         "status": "success",
        #         "errorDescription": null,
        #     }
        #
        id = self.safe_string(transaction, 'id')
        address = self.safe_string(transaction, 'address')
        tag = self.safe_string(transaction, 'paymentId')
        txid = self.safe_value(transaction, 'txId')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        type = self.safe_string(transaction, 'type')
        timestamp = self.parse8601(self.safe_string(transaction, 'createdAt'))
        updated = self.parse8601(self.safe_string(transaction, 'processedAt'))
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        amount = self.safe_number(transaction, 'amount')
        feeCost = self.safe_number(transaction, 'fee')
        fee = {
            'cost': feeCost,
            'currency': code,
        }
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'network': None,
            'address': address,
            'addressTo': None,
            'addressFrom': None,
            'tag': tag,
            'tagTo': None,
            'tagFrom': None,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'fee': fee,
        }

    def fetch_deposit_address(self, code, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :param str code: unified currency code
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/en/latest/manual.html#address-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = self.accountGetDepositAddress(self.extend(request, params))
        #
        #     {
        #         "currency": "BTS",
        #         "address": "crex24",
        #         "paymentId": "0fg4da4186741579"
        #     }
        #
        address = self.safe_string(response, 'address')
        tag = self.safe_string(response, 'paymentId')
        return {
            'currency': code,
            'address': self.check_address(address),
            'tag': tag,
            'network': None,
            'info': response,
        }

    def withdraw(self, code, amount, address, tag=None, params={}):
        """
        make a withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str|None tag:
        :param dict params: extra parameters specific to the crex24 api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
            'address': address,
            'amount': float(self.currency_to_precision(code, amount)),
            # sets whether the specified amount includes fee, can have either of the two values
            # True - balance will be decreased by amount, whereas [amount - fee] will be transferred to the specified address
            # False - amount will be deposited to the specified address, whereas the balance will be decreased by [amount + fee]
            # 'includeFee': False,  # the default value is False
            'feeCurrency': currency['id'],  # https://github.com/ccxt/ccxt/issues/7544
        }
        if tag is not None:
            request['paymentId'] = tag
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
        if network is not None:
            request['transport'] = network
            params = self.omit(params, 'network')
        response = self.accountPostWithdraw(self.extend(request, params))
        return self.parse_transaction(response)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        request = '/' + self.version + '/' + api + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if method == 'GET':
            if query:
                request += '?' + self.urlencode(query)
        url = self.urls['api']['rest'] + request
        if (api == 'trading') or (api == 'account'):
            self.check_required_credentials()
            nonce = str(self.nonce())
            secret = self.base64_to_binary(self.secret)
            auth = request + nonce
            headers = {
                'X-CREX24-API-KEY': self.apiKey,
                'X-CREX24-API-NONCE': nonce,
            }
            if method == 'POST':
                headers['Content-Type'] = 'application/json'
                body = self.json(params)
                auth += body
            headers['X-CREX24-API-SIGN'] = self.hmac(self.encode(auth), secret, hashlib.sha512, 'base64')
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if not self.is_json_encoded_object(body):
            return  # fallback to default error handler
        if (code >= 200) and (code < 300):
            return  # no error
        message = self.safe_string(response, 'errorDescription')
        feedback = self.id + ' ' + body
        self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
        self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
        if code == 400:
            raise BadRequest(feedback)
        elif code == 401:
            raise AuthenticationError(feedback)
        elif code == 403:
            raise AuthenticationError(feedback)
        elif code == 429:
            raise DDoSProtection(feedback)
        elif code == 500:
            raise ExchangeError(feedback)
        elif code == 503:
            raise ExchangeNotAvailable(feedback)
        elif code == 504:
            raise RequestTimeout(feedback)
        raise ExchangeError(feedback)  # unknown message
