# -*- 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.async_support.base.exchange import Exchange
import base64
import hashlib
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import BadRequest
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


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': {
                'cancelAllOrders': True,
                'CORS': False,
                'editOrder': True,
                'fetchBidsAsks': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchFundingFees': False,
                'fetchMyTrades': True,
                'fetchOHLCV': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrders': False,
                'fetchOrderTrades': True,
                'fetchTickers': True,
                'fetchTradingFee': False,  # actually, True, but will be implemented later
                'fetchTradingFees': False,  # actually, True, but will be implemented later
                'fetchTransactions': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/47813922-6f12cc00-dd5d-11e8-97c6-70f957712d47.jpg',
                'api': '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',
                    ],
                },
                'trading': {
                    'get': [
                        'orderStatus',
                        'activeOrders',
                        'orderHistory',
                        'tradeHistory',
                        'tradeFee',
                        # self is in trading API according to their docs, but most likely a typo in their docs
                        'moneyTransferStatus',
                    ],
                    'post': [
                        'placeOrder',
                        'modifyOrder',
                        'cancelOrdersById',
                        'cancelOrdersByInstrument',
                        'cancelAllOrders',
                    ],
                },
                'account': {
                    'get': [
                        'balance',
                        'depositAddress',
                        'moneyTransfers',
                        # self is in trading API according to their docs, but most likely a typo in their docs
                        'moneyTransferStatus',
                        'previewWithdrawal',
                    ],
                    'post': [
                        'withdraw',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': 0.001,
                    'maker': -0.01,
                },
                # should be deleted, these are outdated and inaccurate
                'funding': {
                    'tierBased': False,
                    'percentage': False,
                    'withdraw': {},
                    'deposit': {},
                },
            },
            'commonCurrencies': {
                'YOYO': 'YOYOW',
                'BCC': 'BCH',
            },
            # exchange-specific options
            'options': {
                'fetchTickersMethod': 'publicGetTicker24hr',
                'defaultTimeInForce': 'GTC',  # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel
                'defaultLimitOrderType': 'limit',  # or 'limit_maker'
                '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
            },
            '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,
                },
                'broad': {
                    '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."
                },
            },
        })

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

    async def fetch_markets(self, params={}):
        response = await self.publicGetInstruments(params)
        #
        #     [{             symbol:   "$PAC-BTC",
        #                baseCurrency:   "$PAC",
        #               quoteCurrency:   "BTC",
        #                 feeCurrency:   "BTC",
        #                    tickSize:    1e-8,
        #                    minPrice:    1e-8,
        #                   minVolume:    1,
        #         supportedOrderTypes: ["limit"],
        #                       state:   "active"    },
        #       {             symbol:   "ZZC-USD",
        #                baseCurrency:   "ZZC",
        #               quoteCurrency:   "USD",
        #                 feeCurrency:   "USD",
        #                    tickSize:    0.0001,
        #                    minPrice:    0.0001,
        #                   minVolume:    1,
        #         supportedOrderTypes: ["limit"],
        #                       state:   "active"   }        ]
        #
        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)
            symbol = base + '/' + quote
            tickSize = self.safe_value(market, 'tickSize')
            minPrice = self.safe_value(market, 'minPrice')
            minAmount = self.safe_float(market, 'minVolume')
            precision = {
                'amount': self.precision_from_string(self.number_to_string(minAmount)),
                'price': self.precision_from_string(self.number_to_string(tickSize)),
            }
            active = (market['state'] == 'active')
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'info': market,
                'active': active,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': minAmount,
                        'max': None,
                    },
                    'price': {
                        'min': minPrice,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
            })
        return result

    async def fetch_currencies(self, params={}):
        response = await 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.safe_integer(currency, 'withdrawalPrecision')
            address = self.safe_value(currency, 'BaseAddress')
            active = (currency['depositsAllowed'] and currency['withdrawalsAllowed'] and not currency['isDelisted'])
            type = 'fiat' if currency['isFiat'] else 'crypto'
            result[code] = {
                'id': id,
                'code': code,
                'address': address,
                'info': currency,
                'type': type,
                'name': self.safe_string(currency, 'name'),
                'active': active,
                'fee': self.safe_float(currency, 'flatWithdrawalFee'),  # todo: redesign
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': math.pow(10, -precision),
                        'max': math.pow(10, precision),
                    },
                    'price': {
                        'min': math.pow(10, -precision),
                        'max': math.pow(10, precision),
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                    'deposit': {
                        'min': self.safe_float(currency, 'minDeposit'),
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_float(currency, 'minWithdrawal'),
                        'max': self.safe_float(currency, 'maxWithdrawal'),
                    },
                },
            }
        return result

    async def fetch_balance(self, params={}):
        await self.load_markets()
        request = {
            # 'currency': 'ETH',  # comma-separated list of currency ids
            # 'nonZeroOnly': 'false',  # True by default
        }
        response = await self.accountGetBalance(self.extend(request, params))
        #
        #     [
        #         {
        #           "currency": "ETH",
        #           "available": 0.0,
        #           "reserved": 0.0
        #         }
        #     ]
        #
        # log = require('ololog').unlimited.green
        # log(response)
        # sys.exit()
        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_float(balance, 'available')
            account['used'] = self.safe_float(balance, 'reserved')
            result[code] = account
        return self.parse_balance(result)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'instrument': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default = maximum = 100
        response = await 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, 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'))
        symbol = None
        marketId = self.safe_string(ticker, 'instrument')
        market = self.safe_value(self.markets_by_id, marketId, market)
        if market is not None:
            symbol = market['symbol']
        elif marketId is not None:
            baseId, quoteId = marketId.split('-')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
        last = self.safe_float(ticker, 'last')
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': self.safe_float(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'ask'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,  # previous day close
            'change': None,
            'percentage': self.safe_float(ticker, 'percentChange'),
            'average': None,
            'baseVolume': self.safe_float(ticker, 'baseVolume'),
            'quoteVolume': self.safe_float(ticker, 'quoteVolume'),
            'info': ticker,
        }

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'instrument': market['id'],
        }
        response = await 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)

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        request = {}
        if symbols is not None:
            ids = self.market_ids(symbols)
            request['instrument'] = ','.join(ids)
        response = await 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_tickers(self, tickers, symbols=None):
        result = []
        for i in range(0, len(tickers)):
            result.append(self.parse_ticker(tickers[i]))
        return self.filter_by_array(result, 'symbol', 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'))
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'volume')
        cost = None
        if price is not None:
            if amount is not None:
                cost = amount * price
        id = None
        side = self.safe_string(trade, 'side')
        orderId = self.safe_string(trade, 'orderId')
        symbol = None
        marketId = self.safe_string(trade, 'instrument')
        market = self.safe_value(self.markets_by_id, marketId, market)
        if market is not None:
            symbol = market['symbol']
        fee = None
        feeCurrencyId = self.safe_string(trade, 'feeCurrency')
        feeCode = self.safe_currency_code(feeCurrencyId)
        feeCost = self.safe_float(trade, 'fee')
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': feeCode,
            }
        takerOrMaker = None
        return {
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': None,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': price,
            'cost': cost,
            'amount': amount,
            'fee': fee,
        }

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await 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 = await 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 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 list(statuses.keys())) 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
        #     }
        #
        status = self.parse_order_status(self.safe_string(order, 'status'))
        symbol = self.find_symbol(self.safe_string(order, 'symbol'), market)
        timestamp = self.parse8601(self.safe_string(order, 'timestamp'))
        price = self.safe_float(order, 'price')
        amount = self.safe_float(order, 'volume')
        remaining = self.safe_float(order, 'remainingVolume')
        filled = None
        lastTradeTimestamp = self.parse8601(self.safe_string(order, 'lastUpdate'))
        cost = None
        if remaining is not None:
            if amount is not None:
                filled = amount - remaining
                if self.options['parseOrderToPrecision']:
                    filled = float(self.amount_to_precision(symbol, filled))
                filled = max(filled, 0.0)
                if price is not None:
                    cost = price * filled
        id = self.safe_string(order, 'id')
        type = self.safe_string(order, 'type')
        if type == 'market':
            if price == 0.0:
                if (cost is not None) and(filled is not None):
                    if (cost > 0) and(filled > 0):
                        price = cost / filled
        side = self.safe_string(order, 'side')
        fee = None
        trades = None
        average = None
        if cost is not None:
            if filled:
                average = cost / filled
            if self.options['parseOrderToPrecision']:
                cost = float(self.cost_to_precision(symbol, cost))
        return {
            'info': order,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
            'trades': trades,
        }

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await 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 method requires a price argument for a ' + type + ' order')
            request['price'] = self.price_to_precision(symbol, price)
        if stopPriceIsRequired:
            stopPrice = self.safe_float(params, 'stopPrice')
            if stopPrice is None:
                raise InvalidOrder(self.id + ' createOrder method requires a stopPrice extra param for a ' + type + ' order')
            else:
                request['stopPrice'] = self.price_to_precision(symbol, stopPrice)
        response = await 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)

    async def fetch_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'id': id,
        }
        response = await 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])

    async def fetch_orders_by_ids(self, ids=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            'id': ','.join(ids),
        }
        response = await 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)

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['instrument'] = market['id']
        response = await 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)

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        await 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 = await self.tradingGetActiveOrders(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)

    async def cancel_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'ids': [
                int(id),
            ],
        }
        response = await self.tradingPostCancelOrdersById(self.extend(request, params))
        #
        #     [
        #         465448358,
        #         468364313
        #     ]
        #
        return self.parse_order(response)

    async def cancel_all_orders(self, symbol=None, params={}):
        response = await self.tradingPostCancelAllOrders(params)
        #
        #     [
        #         465448358,
        #         468364313
        #     ]
        #
        return response

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await 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 = await 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)

    async def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        await 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.ymd(since, 'T')
        response = await 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.parseTransactions(response, currency, since, limit)

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        request = {
            'type': 'deposit',
        }
        return self.fetch_transactions(code, since, limit, self.extend(request, params))

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        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_float(transaction, 'amount')
        feeCost = self.safe_float(transaction, 'fee')
        fee = {
            'cost': feeCost,
            'currency': code,
        }
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'address': address,
            'tag': tag,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'fee': fee,
        }

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = await 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,
            'info': response,
        }

    async def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        await 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
        }
        if tag is not None:
            request['paymentId'] = tag
        response = await 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'] + request
        if (api == 'trading') or (api == 'account'):
            self.check_required_credentials()
            nonce = str(self.nonce())
            secret = base64.b64decode(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
            signature = base64.b64encode(self.hmac(self.encode(auth), secret, hashlib.sha512, 'binary'))
            headers['X-CREX24-API-SIGN'] = self.decode(signature)
        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 + ' ' + self.json(response)
        exact = self.exceptions['exact']
        if message in exact:
            raise exact[message](feedback)
        broad = self.exceptions['broad']
        broadKey = self.findBroadlyMatchedKey(broad, message)
        if broadKey is not None:
            raise broad[broadKey](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
