# -*- 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
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidAddress
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import ExchangeNotAvailable


class bitpanda(Exchange):

    def describe(self):
        return self.deep_extend(super(bitpanda, self).describe(), {
            'id': 'bitpanda',
            'name': 'Bitpanda Pro',
            'countries': ['AT'],  # Austria
            'rateLimit': 300,
            'version': 'v1',
            # new metainfo interface
            'has': {
                'CORS': False,
                'publicAPI': True,
                'privateAPI': True,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'cancelOrders': True,
                'createDepositAddress': True,
                'createOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDeposits': True,
                'fetchDepositAddress': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderTrades': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFees': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1/MINUTES',
                '5m': '5/MINUTES',
                '15m': '15/MINUTES',
                '30m': '30/MINUTES',
                '1h': '1/HOURS',
                '4h': '4/HOURS',
                '1d': '1/DAYS',
                '1w': '1/WEEKS',
                '1M': '1/MONTHS',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/87591171-9a377d80-c6f0-11ea-94ac-97a126eac3bc.jpg',
                'api': {
                    'public': 'https://api.exchange.bitpanda.com/public',
                    'private': 'https://api.exchange.bitpanda.com/public',
                },
                'www': 'https://www.bitpanda.com/en/pro',
                'doc': [
                    'https://developers.bitpanda.com/exchange/',
                ],
                'fees': 'https://www.bitpanda.com/en/pro/fees',
            },
            'api': {
                'public': {
                    'get': [
                        'currencies',
                        'candlesticks/{instrument_code}',
                        'fees',
                        'instruments',
                        'order-book/{instrument_code}',
                        'market-ticker',
                        'market-ticker/{instrument_code}',
                        'price-ticks/{instrument_code}',
                        'time',
                    ],
                },
                'private': {
                    'get': [
                        'account/balances',
                        'account/deposit/crypto/{currency_code}',
                        'account/deposit/fiat/EUR',
                        'account/deposits',
                        'account/deposits/bitpanda',
                        'account/withdrawals',
                        'account/withdrawals/bitpanda',
                        'account/fees',
                        'account/orders',
                        'account/orders/{order_id}',
                        'account/orders/{order_id}/trades',
                        'account/trades',
                        'account/trades/{trade_id}',
                        'account/trading-volume',
                    ],
                    'post': [
                        'account/deposit/crypto',
                        'account/withdraw/crypto',
                        'account/withdraw/fiat',
                        'account/fees',
                        'account/orders',
                    ],
                    'delete': [
                        'account/orders',
                        'account/orders/{order_id}',
                        'account/orders/client/{client_id}',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': 0.15 / 100,
                    'maker': 0.10 / 100,
                    'tiers': [
                        # volume in BTC
                        {
                            'taker': [
                                [0, 0.15 / 100],
                                [100, 0.13 / 100],
                                [250, 0.13 / 100],
                                [1000, 0.1 / 100],
                                [5000, 0.09 / 100],
                                [10000, 0.075 / 100],
                                [20000, 0.065 / 100],
                            ],
                            'maker': [
                                [0, 0.1 / 100],
                                [100, 0.1 / 100],
                                [250, 0.09 / 100],
                                [1000, 0.075 / 100],
                                [5000, 0.06 / 100],
                                [10000, 0.05 / 100],
                                [20000, 0.05 / 100],
                            ],
                        },
                    ],
                },
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': False,
            },
            'exceptions': {
                'exact': {
                    'INVALID_CLIENT_UUID': InvalidOrder,
                    'ORDER_NOT_FOUND': OrderNotFound,
                    'ONLY_ONE_ERC20_ADDRESS_ALLOWED': InvalidAddress,
                    'DEPOSIT_ADDRESS_NOT_USED': InvalidAddress,
                    'INVALID_CREDENTIALS': AuthenticationError,
                    'MISSING_CREDENTIALS': AuthenticationError,
                    'INVALID_APIKEY': AuthenticationError,
                    'INVALID_SCOPES': AuthenticationError,
                    'INVALID_SUBJECT': AuthenticationError,
                    'INVALID_ISSUER': AuthenticationError,
                    'INVALID_AUDIENCE': AuthenticationError,
                    'INVALID_DEVICE_ID': AuthenticationError,
                    'INVALID_IP_RESTRICTION': AuthenticationError,
                    'APIKEY_REVOKED': AuthenticationError,
                    'APIKEY_EXPIRED': AuthenticationError,
                    'SYNCHRONIZER_TOKEN_MISMATCH': AuthenticationError,
                    'SESSION_EXPIRED': AuthenticationError,
                    'INTERNAL_ERROR': AuthenticationError,
                    'CLIENT_IP_BLOCKED': PermissionDenied,
                    'MISSING_PERMISSION': PermissionDenied,
                    'ILLEGAL_CHARS': BadRequest,
                    'UNSUPPORTED_MEDIA_TYPE': BadRequest,
                    'ACCOUNT_HISTORY_TIME_RANGE_TOO_BIG': BadRequest,
                    'CANDLESTICKS_TIME_RANGE_TOO_BIG': BadRequest,
                    'INVALID_INSTRUMENT_CODE': BadRequest,
                    'INVALID_ORDER_TYPE': BadRequest,
                    'INVALID_UNIT': BadRequest,
                    'INVALID_PERIOD': BadRequest,
                    'INVALID_TIME': BadRequest,
                    'INVALID_DATE': BadRequest,
                    'INVALID_CURRENCY': BadRequest,
                    'INVALID_AMOUNT': BadRequest,
                    'INVALID_PRICE': BadRequest,
                    'INVALID_LIMIT': BadRequest,
                    'INVALID_QUERY': BadRequest,
                    'INVALID_CURSOR': BadRequest,
                    'INVALID_ACCOUNT_ID': BadRequest,
                    'INVALID_SIDE': InvalidOrder,
                    'INVALID_ACCOUNT_HISTORY_FROM_TIME': BadRequest,
                    'INVALID_ACCOUNT_HISTORY_MAX_PAGE_SIZE': BadRequest,
                    'INVALID_ACCOUNT_HISTORY_TIME_PERIOD': BadRequest,
                    'INVALID_ACCOUNT_HISTORY_TO_TIME': BadRequest,
                    'INVALID_CANDLESTICKS_GRANULARITY': BadRequest,
                    'INVALID_CANDLESTICKS_UNIT': BadRequest,
                    'INVALID_ORDER_BOOK_DEPTH': BadRequest,
                    'INVALID_ORDER_BOOK_LEVEL': BadRequest,
                    'INVALID_PAGE_CURSOR': BadRequest,
                    'INVALID_TIME_RANGE': BadRequest,
                    'INVALID_TRADE_ID': BadRequest,
                    'INVALID_UI_ACCOUNT_SETTINGS': BadRequest,
                    'NEGATIVE_AMOUNT': InvalidOrder,
                    'NEGATIVE_PRICE': InvalidOrder,
                    'MIN_SIZE_NOT_SATISFIED': InvalidOrder,
                    'BAD_AMOUNT_PRECISION': InvalidOrder,
                    'BAD_PRICE_PRECISION': InvalidOrder,
                    'BAD_TRIGGER_PRICE_PRECISION': InvalidOrder,
                    'MAX_OPEN_ORDERS_EXCEEDED': BadRequest,
                    'MISSING_PRICE': InvalidOrder,
                    'MISSING_ORDER_TYPE': InvalidOrder,
                    'MISSING_SIDE': InvalidOrder,
                    'MISSING_CANDLESTICKS_PERIOD_PARAM': ArgumentsRequired,
                    'MISSING_CANDLESTICKS_UNIT_PARAM': ArgumentsRequired,
                    'MISSING_FROM_PARAM': ArgumentsRequired,
                    'MISSING_INSTRUMENT_CODE': ArgumentsRequired,
                    'MISSING_ORDER_ID': InvalidOrder,
                    'MISSING_TO_PARAM': ArgumentsRequired,
                    'MISSING_TRADE_ID': ArgumentsRequired,
                    'INVALID_ORDER_ID': OrderNotFound,
                    'NOT_FOUND': OrderNotFound,
                    'INSUFFICIENT_LIQUIDITY': InsufficientFunds,
                    'INSUFFICIENT_FUNDS': InsufficientFunds,
                    'NO_TRADING': ExchangeNotAvailable,
                    'SERVICE_UNAVAILABLE': ExchangeNotAvailable,
                    'GATEWAY_TIMEOUT': ExchangeNotAvailable,
                    'RATELIMIT': DDoSProtection,
                    'CF_RATELIMIT': DDoSProtection,
                    'INTERNAL_SERVER_ERROR': ExchangeError,
                },
                'broad': {
                },
            },
            'commonCurrencies': {
                'MIOTA': 'IOTA',  # https://github.com/ccxt/ccxt/issues/7487
            },
            # exchange-specific options
            'options': {
                'fetchTradingFees': {
                    'method': 'fetchPrivateTradingFees',  # or 'fetchPublicTradingFees'
                },
                'fiat': ['EUR', 'CHF'],
            },
        })

    def fetch_time(self, params={}):
        response = self.publicGetTime(params)
        #
        #     {
        #         iso: '2020-07-10T05:17:26.716Z',
        #         epoch_millis: 1594358246716,
        #     }
        #
        return self.safe_integer(response, 'epoch_millis')

    def fetch_currencies(self, params={}):
        response = self.publicGetCurrencies(params)
        #
        #     [
        #         {
        #             "code":"BEST",
        #             "precision":8
        #         }
        #     ]
        #
        result = {}
        for i in range(0, len(response)):
            currency = response[i]
            id = self.safe_string(currency, 'code')
            code = self.safe_currency_code(id)
            result[code] = {
                'id': id,
                'code': code,
                'name': None,
                'info': currency,  # the original payload
                'active': None,
                'fee': None,
                'precision': self.safe_integer(currency, 'precision'),
                'limits': {
                    'amount': {'min': None, 'max': None},
                    'price': {'min': None, 'max': None},
                    'cost': {'min': None, 'max': None},
                    'withdraw': {'min': None, 'max': None},
                },
            }
        return result

    def fetch_markets(self, params={}):
        response = self.publicGetInstruments(params)
        #
        #     [
        #         {
        #             state: 'ACTIVE',
        #             base: {code: 'ETH', precision: 8},
        #             quote: {code: 'CHF', precision: 2},
        #             amount_precision: 4,
        #             market_precision: 2,
        #             min_size: '10.0'
        #         }
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            market = response[i]
            baseAsset = self.safe_value(market, 'base', {})
            quoteAsset = self.safe_value(market, 'quote', {})
            baseId = self.safe_string(baseAsset, 'code')
            quoteId = self.safe_string(quoteAsset, 'code')
            id = baseId + '_' + quoteId
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            precision = {
                'amount': self.safe_integer(market, 'amount_precision'),
                'price': self.safe_integer(market, 'market_precision'),
            }
            limits = {
                'amount': {
                    'min': None,
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': self.safe_float(market, 'min_size'),
                    'max': None,
                },
            }
            state = self.safe_string(market, 'state')
            active = (state == 'ACTIVE')
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'precision': precision,
                'limits': limits,
                'info': market,
                'active': active,
            })
        return result

    def fetch_trading_fees(self, params={}):
        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.publicGetFees(params)
        #
        #     [
        #         {
        #             "fee_group_id":"default",
        #             "display_text":"The standard fee plan.",
        #             "fee_tiers":[
        #                 {"volume":"0.0","fee_group_id":"default","maker_fee":"0.1","taker_fee":"0.15"},
        #                 {"volume":"100.0","fee_group_id":"default","maker_fee":"0.1","taker_fee":"0.13"},
        #                 {"volume":"250.0","fee_group_id":"default","maker_fee":"0.09","taker_fee":"0.13"},
        #                 {"volume":"1000.0","fee_group_id":"default","maker_fee":"0.075","taker_fee":"0.1"},
        #                 {"volume":"5000.0","fee_group_id":"default","maker_fee":"0.06","taker_fee":"0.09"},
        #                 {"volume":"10000.0","fee_group_id":"default","maker_fee":"0.05","taker_fee":"0.075"},
        #                 {"volume":"20000.0","fee_group_id":"default","maker_fee":"0.05","taker_fee":"0.065"}
        #             ],
        #             "fee_discount_rate":"25.0",
        #             "minimum_price_value":"0.12"
        #         }
        #     ]
        #
        feeGroupsById = self.index_by(response, 'fee_group_id')
        feeGroupId = self.safe_value(self.options, 'fee_group_id', 'default')
        feeGroup = self.safe_value(feeGroupsById, feeGroupId, {})
        feeTiers = self.safe_value(feeGroup, 'fee_tiers')
        result = {}
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            fee = {
                'info': feeGroup,
                'symbol': symbol,
                'maker': None,
                'taker': None,
                'percentage': True,
                'tierBased': True,
            }
            takerFees = []
            makerFees = []
            for i in range(0, len(feeTiers)):
                tier = feeTiers[i]
                volume = self.safe_float(tier, 'volume')
                taker = self.safe_float(tier, 'taker_fee')
                maker = self.safe_float(tier, 'maker_fee')
                taker /= 100
                maker /= 100
                takerFees.append([volume, taker])
                makerFees.append([volume, maker])
                if i == 0:
                    fee['taker'] = taker
                    fee['maker'] = maker
            tiers = {
                'taker': takerFees,
                'maker': makerFees,
            }
            fee['tiers'] = tiers
            result[symbol] = fee
        return result

    def fetch_private_trading_fees(self, params={}):
        self.load_markets()
        response = self.privateGetAccountFees(params)
        #
        #     {
        #         "account_id": "ed524d00-820a-11e9-8f1e-69602df16d85",
        #         "running_trading_volume": "0.0",
        #         "fee_group_id": "default",
        #         "collect_fees_in_best": False,
        #         "fee_discount_rate": "25.0",
        #         "minimum_price_value": "0.12",
        #         "fee_tiers": [
        #             {"volume": "0.0", "fee_group_id": "default", "maker_fee": "0.1", "taker_fee": "0.1"},
        #             {"volume": "100.0", "fee_group_id": "default", "maker_fee": "0.09", "taker_fee": "0.1"},
        #             {"volume": "250.0", "fee_group_id": "default", "maker_fee": "0.08", "taker_fee": "0.1"},
        #             {"volume": "1000.0", "fee_group_id": "default", "maker_fee": "0.07", "taker_fee": "0.09"},
        #             {"volume": "5000.0", "fee_group_id": "default", "maker_fee": "0.06", "taker_fee": "0.08"},
        #             {"volume": "10000.0", "fee_group_id": "default", "maker_fee": "0.05", "taker_fee": "0.07"},
        #             {"volume": "20000.0", "fee_group_id": "default", "maker_fee": "0.05", "taker_fee": "0.06"},
        #             {"volume": "50000.0", "fee_group_id": "default", "maker_fee": "0.05", "taker_fee": "0.05"}
        #         ],
        #         "active_fee_tier": {"volume": "0.0", "fee_group_id": "default", "maker_fee": "0.1", "taker_fee": "0.1"}
        #     }
        #
        activeFeeTier = self.safe_value(response, 'active_fee_tier', {})
        result = {
            'info': response,
            'maker': self.safe_float(activeFeeTier, 'maker_fee'),
            'taker': self.safe_float(activeFeeTier, 'taker_fee'),
            'percentage': True,
            'tierBased': True,
        }
        feeTiers = self.safe_value(response, 'fee_tiers')
        takerFees = []
        makerFees = []
        for i in range(0, len(feeTiers)):
            tier = feeTiers[i]
            volume = self.safe_float(tier, 'volume')
            taker = self.safe_float(tier, 'taker_fee')
            maker = self.safe_float(tier, 'maker_fee')
            taker /= 100
            maker /= 100
            takerFees.append([volume, taker])
            makerFees.append([volume, maker])
        tiers = {
            'taker': takerFees,
            'maker': makerFees,
        }
        result['tiers'] = tiers
        return result

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker, fetchTickers
        #
        #     {
        #         "instrument_code":"BTC_EUR",
        #         "sequence":602562,
        #         "time":"2020-07-10T06:27:34.951Z",
        #         "state":"ACTIVE",
        #         "is_frozen":0,
        #         "quote_volume":"1695555.1783768",
        #         "base_volume":"205.67436",
        #         "last_price":"8143.91",
        #         "best_bid":"8143.71",
        #         "best_ask":"8156.9",
        #         "price_change":"-147.47",
        #         "price_change_percentage":"-1.78",
        #         "high":"8337.45",
        #         "low":"8110.0"
        #     }
        #
        timestamp = self.parse8601(self.safe_string(ticker, 'time'))
        marketId = self.safe_string(ticker, 'instrument_code')
        symbol = self.safe_symbol(marketId, market, '_')
        last = self.safe_float(ticker, 'last_price')
        percentage = self.safe_float(ticker, 'price_change_percentage')
        change = self.safe_float(ticker, 'price_change')
        open = None
        average = None
        if (last is not None) and (change is not None):
            open = last - change
            average = self.sum(last, open) / 2
        baseVolume = self.safe_float(ticker, 'base_volume')
        quoteVolume = self.safe_float(ticker, 'quote_volume')
        vwap = self.vwap(baseVolume, quoteVolume)
        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, 'best_bid'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'best_ask'),
            'askVolume': None,
            'vwap': vwap,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'instrument_code': market['id'],
        }
        response = self.publicGetMarketTickerInstrumentCode(self.extend(request, params))
        #
        #     {
        #         "instrument_code":"BTC_EUR",
        #         "sequence":602562,
        #         "time":"2020-07-10T06:27:34.951Z",
        #         "state":"ACTIVE",
        #         "is_frozen":0,
        #         "quote_volume":"1695555.1783768",
        #         "base_volume":"205.67436",
        #         "last_price":"8143.91",
        #         "best_bid":"8143.71",
        #         "best_ask":"8156.9",
        #         "price_change":"-147.47",
        #         "price_change_percentage":"-1.78",
        #         "high":"8337.45",
        #         "low":"8110.0"
        #     }
        #
        return self.parse_ticker(response, market)

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        response = self.publicGetMarketTicker(params)
        #
        #     [
        #         {
        #             "instrument_code":"BTC_EUR",
        #             "sequence":602562,
        #             "time":"2020-07-10T06:27:34.951Z",
        #             "state":"ACTIVE",
        #             "is_frozen":0,
        #             "quote_volume":"1695555.1783768",
        #             "base_volume":"205.67436",
        #             "last_price":"8143.91",
        #             "best_bid":"8143.71",
        #             "best_ask":"8156.9",
        #             "price_change":"-147.47",
        #             "price_change_percentage":"-1.78",
        #             "high":"8337.45",
        #             "low":"8110.0"
        #         }
        #     ]
        #
        result = {}
        for i in range(0, len(response)):
            ticker = self.parse_ticker(response[i])
            symbol = ticker['symbol']
            result[symbol] = ticker
        return self.filter_by_array(result, 'symbol', symbols)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        request = {
            'instrument_code': self.market_id(symbol),
            # level 1 means only the best bid and ask
            # level 2 is a compiled order book up to market precision
            # level 3 is a full orderbook
            # if you wish to get regular updates about orderbooks please use the Websocket channel
            # heavy usage of self endpoint may result in limited access according to rate limits rules
            # 'level': 3,  # default
        }
        if limit is not None:
            request['depth'] = limit
        response = self.publicGetOrderBookInstrumentCode(self.extend(request, params))
        #
        # level 1
        #
        #     {
        #         "instrument_code":"BTC_EUR",
        #         "time":"2020-07-10T07:39:06.343Z",
        #         "asks":{
        #             "value":{
        #                 "price":"8145.29",
        #                 "amount":"0.96538",
        #                 "number_of_orders":1
        #             }
        #         },
        #         "bids":{
        #             "value":{
        #                 "price":"8134.0",
        #                 "amount":"1.5978",
        #                 "number_of_orders":5
        #             }
        #         }
        #     }
        #
        # level 2
        #
        #     {
        #         "instrument_code":"BTC_EUR","time":"2020-07-10T07:36:43.538Z",
        #         "asks":[
        #             {"price":"8146.59","amount":"0.89691","number_of_orders":1},
        #             {"price":"8146.89","amount":"1.92062","number_of_orders":1},
        #             {"price":"8169.5","amount":"0.0663","number_of_orders":1},
        #         ],
        #         "bids":[
        #             {"price":"8143.49","amount":"0.01329","number_of_orders":1},
        #             {"price":"8137.01","amount":"5.34748","number_of_orders":1},
        #             {"price":"8137.0","amount":"2.0","number_of_orders":1},
        #         ]
        #     }
        #
        # level 3
        #
        #     {
        #         "instrument_code":"BTC_EUR",
        #         "time":"2020-07-10T07:32:31.525Z",
        #         "bids":[
        #             {"price":"8146.79","amount":"0.01537","order_id":"5d717da1-a8f4-422d-afcc-03cb6ab66825"},
        #             {"price":"8139.32","amount":"3.66009","order_id":"d0715c68-f28d-4cf1-a450-d56cf650e11c"},
        #             {"price":"8137.51","amount":"2.61049","order_id":"085fd6f4-e835-4ca5-9449-a8f165772e60"},
        #         ],
        #         "asks":[
        #             {"price":"8153.49","amount":"0.93384","order_id":"755d3aa3-42b5-46fa-903d-98f42e9ae6c4"},
        #             {"price":"8153.79","amount":"1.80456","order_id":"62034cf3-b70d-45ff-b285-ba6307941e7c"},
        #             {"price":"8167.9","amount":"0.0018","order_id":"036354e0-71cd-492f-94f2-01f7d4b66422"},
        #         ]
        #     }
        #
        timestamp = self.parse8601(self.safe_string(response, 'time'))
        return self.parse_order_book(response, timestamp, 'bids', 'asks', 'price', 'amount')

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "instrument_code":"BTC_EUR",
        #         "granularity":{"unit":"HOURS","period":1},
        #         "high":"9252.65",
        #         "low":"9115.27",
        #         "open":"9250.0",
        #         "close":"9132.35",
        #         "total_amount":"33.85924",
        #         "volume":"311958.9635744",
        #         "time":"2020-05-08T22:59:59.999Z",
        #         "last_sequence":461123
        #     }
        #
        granularity = self.safe_value(ohlcv, 'granularity')
        unit = self.safe_string(granularity, 'unit')
        period = self.safe_string(granularity, 'period')
        units = {
            'MINUTES': 'm',
            'HOURS': 'h',
            'DAYS': 'd',
            'WEEKS': 'w',
            'MONTHS': 'M',
        }
        lowercaseUnit = self.safe_string(units, unit)
        timeframe = period + lowercaseUnit
        durationInSeconds = self.parse_timeframe(timeframe)
        duration = durationInSeconds * 1000
        timestamp = self.parse8601(self.safe_string(ohlcv, 'time'))
        alignedTimestamp = duration * int(timestamp / duration)
        options = self.safe_value(self.options, 'fetchOHLCV', {})
        volumeField = self.safe_string(options, 'volume', 'total_amount')
        return [
            alignedTimestamp,
            self.safe_float(ohlcv, 'open'),
            self.safe_float(ohlcv, 'high'),
            self.safe_float(ohlcv, 'low'),
            self.safe_float(ohlcv, 'close'),
            self.safe_float(ohlcv, volumeField),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        periodUnit = self.safe_string(self.timeframes, timeframe)
        period, unit = periodUnit.split('/')
        durationInSeconds = self.parse_timeframe(timeframe)
        duration = durationInSeconds * 1000
        if limit is None:
            limit = 1500
        request = {
            'instrument_code': market['id'],
            # 'from': self.iso8601(since),
            # 'to': self.iso8601(self.milliseconds()),
            'period': period,
            'unit': unit,
        }
        if since is None:
            now = self.milliseconds()
            request['to'] = self.iso8601(now)
            request['from'] = self.iso8601(now - limit * duration)
        else:
            request['from'] = self.iso8601(since)
            request['to'] = self.iso8601(self.sum(since, limit * duration))
        response = self.publicGetCandlesticksInstrumentCode(self.extend(request, params))
        #
        #     [
        #         {"instrument_code":"BTC_EUR","granularity":{"unit":"HOURS","period":1},"high":"9252.65","low":"9115.27","open":"9250.0","close":"9132.35","total_amount":"33.85924","volume":"311958.9635744","time":"2020-05-08T22:59:59.999Z","last_sequence":461123},
        #         {"instrument_code":"BTC_EUR","granularity":{"unit":"HOURS","period":1},"high":"9162.49","low":"9040.0","open":"9132.53","close":"9083.69","total_amount":"26.19685","volume":"238553.7812365","time":"2020-05-08T23:59:59.999Z","last_sequence":461376},
        #         {"instrument_code":"BTC_EUR","granularity":{"unit":"HOURS","period":1},"high":"9135.7","low":"9002.59","open":"9055.45","close":"9133.98","total_amount":"26.21919","volume":"238278.8724959","time":"2020-05-09T00:59:59.999Z","last_sequence":461521},
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         "instrument_code":"BTC_EUR",
        #         "price":"8137.28",
        #         "amount":"0.22269",
        #         "taker_side":"BUY",
        #         "volume":"1812.0908832",
        #         "time":"2020-07-10T14:44:32.299Z",
        #         "trade_timestamp":1594392272299,
        #         "sequence":603047
        #     }
        #
        # fetchMyTrades, fetchOrder, fetchOpenOrders, fetchClosedOrders trades(private)
        #
        #     {
        #         "fee": {
        #             "fee_amount": "0.0014",
        #             "fee_currency": "BTC",
        #             "fee_percentage": "0.1",
        #             "fee_group_id": "default",
        #             "fee_type": "TAKER",
        #             "running_trading_volume": "0.0"
        #         },
        #         "trade": {
        #             "trade_id": "fdff2bcc-37d6-4a2d-92a5-46e09c868664",
        #             "order_id": "36bb2437-7402-4794-bf26-4bdf03526439",
        #             "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9",
        #             "amount": "1.4",
        #             "side": "BUY",
        #             "instrument_code": "BTC_EUR",
        #             "price": "7341.4",
        #             "time": "2019-09-27T15:05:32.564Z",
        #             "sequence": 48670
        #         }
        #     }
        #
        feeInfo = self.safe_value(trade, 'fee', {})
        trade = self.safe_value(trade, 'trade', trade)
        timestamp = self.safe_integer(trade, 'trade_timestamp')
        if timestamp is None:
            timestamp = self.parse8601(self.safe_string(trade, 'time'))
        side = self.safe_string_lower_2(trade, 'side', 'taker_side')
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'amount')
        cost = self.safe_float(trade, 'volume')
        if (cost is None) and (amount is not None) and (price is not None):
            cost = amount * price
        marketId = self.safe_string(trade, 'instrument_code')
        symbol = self.safe_symbol(marketId, market, '_')
        feeCost = self.safe_float(feeInfo, 'fee_amount')
        takerOrMaker = None
        fee = None
        if feeCost is not None:
            feeCurrencyId = self.safe_string(feeInfo, 'fee_currency')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            feeRate = self.safe_float(feeInfo, 'fee_percentage')
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
                'rate': feeRate,
            }
            takerOrMaker = self.safe_string_lower(feeInfo, 'fee_type')
        return {
            'id': self.safe_string_2(trade, 'trade_id', 'sequence'),
            'order': self.safe_string(trade, 'order_id'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': None,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'takerOrMaker': takerOrMaker,
            'fee': fee,
            'info': trade,
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'instrument_code': market['id'],
            # 'from': self.iso8601(since),
            # 'to': self.iso8601(self.milliseconds()),
        }
        if since is not None:
            # returns price ticks for a specific market with an interval of maximum of 4 hours
            # sorted by latest first
            request['from'] = self.iso8601(since)
            request['to'] = self.iso8601(self.sum(since, 14400000))
        response = self.publicGetPriceTicksInstrumentCode(self.extend(request, params))
        #
        #     [
        #         {
        #             "instrument_code":"BTC_EUR",
        #             "price":"8137.28",
        #             "amount":"0.22269",
        #             "taker_side":"BUY",
        #             "volume":"1812.0908832",
        #             "time":"2020-07-10T14:44:32.299Z",
        #             "trade_timestamp":1594392272299,
        #             "sequence":603047
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def fetch_balance(self, params={}):
        self.load_markets()
        response = self.privateGetAccountBalances(params)
        #
        #     {
        #         "account_id":"4b95934f-55f1-460c-a525-bd5afc0cf071",
        #         "balances":[
        #             {
        #                 "account_id":"4b95934f-55f1-460c-a525-bd5afc0cf071",
        #                 "currency_code":"BTC",
        #                 "change":"10.0",
        #                 "available":"10.0",
        #                 "locked":"0.0",
        #                 "sequence":142135994,
        #                 "time":"2020-07-01T10:57:32.959Z"
        #             }
        #         ]
        #     }
        #
        balances = self.safe_value(response, 'balances', [])
        result = {'info': response}
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string(balance, 'currency_code')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_float(balance, 'available')
            account['used'] = self.safe_float(balance, 'locked')
            result[code] = account
        return self.parse_balance(result)

    def parse_deposit_address(self, depositAddress, currency=None):
        code = None
        if currency is not None:
            code = currency['code']
        address = self.safe_string(depositAddress, 'address')
        tag = self.safe_string(depositAddress, 'destination_tag')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': depositAddress,
        }

    def create_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = self.privatePostAccountDepositCrypto(self.extend(request, params))
        #
        #     {
        #         "address":"rBnNhk95FrdNisZtXcStzriFS8vEzz53DM",
        #         "destination_tag":"865690307",
        #         "enabled":true,
        #         "is_smart_contract":false
        #     }
        #
        return self.parse_deposit_address(response, currency)

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency_code': currency['id'],
        }
        response = self.privateGetAccountDepositCryptoCurrencyCode(self.extend(request, params))
        #
        #     {
        #         "address":"rBnNhk95FrdNisZtXcStzriFS8vEzz53DM",
        #         "destination_tag":"865690307",
        #         "enabled":true,
        #         "is_smart_contract":false,
        #         "can_create_more":false
        #     }
        #
        return self.parse_deposit_address(response, currency)

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'cursor': 'string',  # pointer specifying the position from which the next pages should be returned
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency_code'] = currency['id']
        if limit is not None:
            request['max_page_size'] = limit
        if since is not None:
            to = self.safe_string(params, 'to')
            if to is None:
                raise ArgumentsRequired(self.id + ' fetchDeposits() requires a "to" iso8601 string param with the since argument is specified')
            request['from'] = self.iso8601(since)
        response = self.privateGetAccountDeposits(self.extend(request, params))
        #
        #     {
        #         "deposit_history": [
        #             {
        #                 "transaction_id": "e5342efcd-d5b7-4a56-8e12-b69ffd68c5ef",
        #                 "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58",
        #                 "amount": "100",
        #                 "type": "CRYPTO",
        #                 "funds_source": "INTERNAL",
        #                 "time": "2020-04-22T09:57:47Z",
        #                 "currency": "BTC",
        #                 "fee_amount": "0.0",
        #                 "fee_currency": "BTC"
        #             },
        #             {
        #                 "transaction_id": "79793d00-2899-4a4d-95b7-73ae6b31384f",
        #                 "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58",
        #                 "time": "2020-05-05T11:22:07.925Z",
        #                 "currency": "EUR",
        #                 "funds_source": "EXTERNAL",
        #                 "type": "FIAT",
        #                 "amount": "50.0",
        #                 "fee_amount": "0.01",
        #                 "fee_currency": "EUR"
        #             }
        #         ],
        #         "max_page_size": 2,
        #         "cursor": "eyJhY2NvdW50X2lkIjp7InMiOiJlMzY5YWM4MC00NTc3LTExZTktYWUwOC05YmVkYzQ3OTBiODQiLCJzcyI6W10sIm5zIjpbXSwiYnMiOltdLCJtIjp7fSwibCI6W119LCJpdGVtX2tleSI6eyJzIjoiV0lUSERSQVdBTDo6MmFlMjYwY2ItOTk3MC00YmNiLTgxNmEtZGY4MDVmY2VhZTY1Iiwic3MiOltdLCJucyI6W10sImJzIjpbXSwibSI6e30sImwiOltdfSwiZ2xvYmFsX3dpdGhkcmF3YWxfaW5kZXhfaGFzaF9rZXkiOnsicyI6ImUzNjlhYzgwLTQ1NzctMTFlOS1hZTA4LTliZWRjNDc5MGI4NCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX0sInRpbWVzdGFtcCI6eyJuIjoiMTU4ODA1ODc2Nzk0OCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX19"
        #     }
        #
        depositHistory = self.safe_value(response, 'deposit_history', [])
        return self.parse_transactions(depositHistory, currency, since, limit, {'type': 'deposit'})

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'cursor': 'string',  # pointer specifying the position from which the next pages should be returned
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency_code'] = currency['id']
        if limit is not None:
            request['max_page_size'] = limit
        if since is not None:
            to = self.safe_string(params, 'to')
            if to is None:
                raise ArgumentsRequired(self.id + ' fetchWithdrawals() requires a "to" iso8601 string param with the since argument is specified')
            request['from'] = self.iso8601(since)
        response = self.privateGetAccountWithdrawals(self.extend(request, params))
        #
        #     {
        #         "withdrawal_history": [
        #             {
        #                 "account_id": "e369ac80-4577-11e9-ae08-9bedc4790b84",
        #                 "amount": "0.1",
        #                 "currency": "BTC",
        #                 "fee_amount": "0.00002",
        #                 "fee_currency": "BTC",
        #                 "funds_source": "EXTERNAL",
        #                 "related_transaction_id": "e298341a-3855-405e-bce3-92db368a3157",
        #                 "time": "2020-05-05T11:11:32.110Z",
        #                 "transaction_id": "6693ff40-bb10-4dcf-ada7-3b287727c882",
        #                 "type": "CRYPTO"
        #             },
        #             {
        #                 "account_id": "e369ac80-4577-11e9-ae08-9bedc4790b84",
        #                 "amount": "0.1",
        #                 "currency": "BTC",
        #                 "fee_amount": "0.0",
        #                 "fee_currency": "BTC",
        #                 "funds_source": "INTERNAL",
        #                 "time": "2020-05-05T10:29:53.464Z",
        #                 "transaction_id": "ec9703b1-954b-4f76-adea-faac66eabc0b",
        #                 "type": "CRYPTO"
        #             }
        #         ],
        #         "cursor": "eyJhY2NvdW50X2lkIjp7InMiOiJlMzY5YWM4MC00NTc3LTExZTktYWUwOC05YmVkYzQ3OTBiODQiLCJzcyI6W10sIm5zIjpbXSwiYnMiOltdLCJtIjp7fSwibCI6W119LCJpdGVtX2tleSI6eyJzIjoiV0lUSERSQVdBTDo6ZWM5NzAzYjEtOTU0Yi00Zjc2LWFkZWEtZmFhYzY2ZWFiYzBiIiwic3MiOltdLCJucyI6W10sImJzIjpbXSwibSI6e30sImwiOltdfSwiZ2xvYmFsX3dpdGhkcmF3YWxfaW5kZXhfaGFzaF9rZXkiOnsicyI6ImUzNjlhYzgwLTQ1NzctMTFlOS1hZTA4LTliZWRjNDc5MGI4NCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX0sInRpbWVzdGFtcCI6eyJuIjoiMTU4ODY3NDU5MzQ2NCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX19",
        #         "max_page_size": 2
        #     }
        #
        withdrawalHistory = self.safe_value(response, 'withdrawal_history', [])
        return self.parse_transactions(withdrawalHistory, currency, since, limit, {'type': 'withdrawal'})

    def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': code,
            'amount': self.currency_to_precision(code, amount),
            # 'payout_account_id': '66756a10-3e86-48f4-9678-b634c4b135b2',  # fiat only
            # 'recipient': { # crypto only
            #     'address': address,
            #     # 'destination_tag': '',
            # },
        }
        options = self.safe_value(self.options, 'fiat', [])
        isFiat = self.in_array(code, options)
        method = 'privatePostAccountWithdrawFiat' if isFiat else 'privatePostAccountWithdrawCrypto'
        if isFiat:
            payoutAccountId = self.safe_string(params, 'payout_account_id')
            if payoutAccountId is None:
                raise ArgumentsRequired(self.id + ' withdraw() requires a payout_account_id param for fiat ' + code + ' withdrawals')
        else:
            recipient = {'address': address}
            if tag is not None:
                recipient['destination_tag'] = tag
            request['recipient'] = recipient
        response = getattr(self, method)(self.extend(request, params))
        #
        # crypto
        #
        #     {
        #         "amount": "1234.5678",
        #         "fee": "1234.5678",
        #         "recipient": "3NacQ7rzZdhfyAtfJ5a11k8jFPdcMP2Bq7",
        #         "destination_tag": "",
        #         "transaction_id": "d0f8529f-f832-4e6a-9dc5-b8d5797badb2"
        #     }
        #
        # fiat
        #
        #     {
        #         "transaction_id": "54236cd0-4413-11e9-93fb-5fea7e5b5df6"
        #     }
        #
        return self.parse_transaction(response, currency)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits, fetchWithdrawals
        #
        #     {
        #         "transaction_id": "C2b42efcd-d5b7-4a56-8e12-b69ffd68c5ef",
        #         "type": "FIAT",
        #         "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58",
        #         "amount": "1234.5678",
        #         "time": "2019-08-24T14:15:22Z",
        #         "funds_source": "INTERNAL",
        #         "currency": "BTC",
        #         "fee_amount": "1234.5678",
        #         "fee_currency": "BTC",
        #         "blockchain_transaction_id": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16",
        #         "related_transaction_id": "e298341a-3855-405e-bce3-92db368a3157"
        #     }
        #
        # withdraw
        #
        #
        #     crypto
        #
        #     {
        #         "amount": "1234.5678",
        #         "fee": "1234.5678",
        #         "recipient": "3NacQ7rzZdhfyAtfJ5a11k8jFPdcMP2Bq7",
        #         "destination_tag": "",
        #         "transaction_id": "d0f8529f-f832-4e6a-9dc5-b8d5797badb2"
        #     }
        #
        #     fiat
        #
        #     {
        #         "transaction_id": "54236cd0-4413-11e9-93fb-5fea7e5b5df6"
        #     }
        #
        id = self.safe_string(transaction, 'transaction_id')
        amount = self.safe_float(transaction, 'amount')
        timestamp = self.parse8601(self.safe_string(transaction, 'time'))
        currencyId = self.safe_string(transaction, 'currency')
        currency = self.safe_currency(currencyId, currency)
        status = 'ok'  # the exchange returns cleared transactions only
        feeCost = self.safe_float_2(transaction, 'fee_amount', 'fee')
        fee = None
        addressTo = self.safe_string(transaction, 'recipient')
        tagTo = self.safe_string(transaction, 'destination_tag')
        if feeCost is not None:
            feeCurrencyId = self.safe_string(transaction, 'fee_currency', currencyId)
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        return {
            'info': transaction,
            'id': id,
            'currency': currency['code'],
            'amount': amount,
            'address': addressTo,
            'addressFrom': None,
            'addressTo': addressTo,
            'tag': tagTo,
            'tagFrom': None,
            'tagTo': tagTo,
            'status': status,
            'type': None,
            'updated': None,
            'txid': self.safe_string(transaction, 'blockchain_transaction_id'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': fee,
        }

    def parse_order_status(self, status):
        statuses = {
            'FILLED': 'open',
            'FILLED_FULLY': 'closed',
            'FILLED_CLOSED': 'canceled',
            'FILLED_REJECTED': 'rejected',
            'OPEN': 'open',
            'REJECTED': 'rejected',
            'CLOSED': 'canceled',
            'FAILED': 'failed',
            'STOP_TRIGGERED': 'triggered',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "order_id": "d5492c24-2995-4c18-993a-5b8bf8fffc0d",
        #         "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206",
        #         "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9",
        #         "instrument_code": "BTC_EUR",
        #         "time": "2019-08-01T08:00:44.026Z",
        #         "side": "BUY",
        #         "price": "5000",
        #         "amount": "1",
        #         "filled_amount": "0.5",
        #         "type": "LIMIT",
        #         "time_in_force": "GOOD_TILL_CANCELLED"
        #     }
        #
        # fetchOrder, fetchOpenOrders, fetchClosedOrders
        #
        #     {
        #         "order": {
        #             "order_id": "66756a10-3e86-48f4-9678-b634c4b135b2",
        #             "account_id": "1eb2ad5d-55f1-40b5-bc92-7dc05869e905",
        #             "instrument_code": "BTC_EUR",
        #             "amount": "1234.5678",
        #             "filled_amount": "1234.5678",
        #             "side": "BUY",
        #             "type": "LIMIT",
        #             "status": "OPEN",
        #             "sequence": 123456789,
        #             "price": "1234.5678",
        #             "average_price": "1234.5678",
        #             "reason": "INSUFFICIENT_FUNDS",
        #             "time": "2019-08-24T14:15:22Z",
        #             "time_in_force": "GOOD_TILL_CANCELLED",
        #             "time_last_updated": "2019-08-24T14:15:22Z",
        #             "expire_after": "2019-08-24T14:15:22Z",
        #             "is_post_only": False,
        #             "time_triggered": "2019-08-24T14:15:22Z",
        #             "trigger_price": "1234.5678"
        #         },
        #         "trades": [
        #             {
        #                 "fee": {
        #                     "fee_amount": "0.0014",
        #                     "fee_currency": "BTC",
        #                     "fee_percentage": "0.1",
        #                     "fee_group_id": "default",
        #                     "fee_type": "TAKER",
        #                     "running_trading_volume": "0.0"
        #                 },
        #                 "trade": {
        #                     "trade_id": "fdff2bcc-37d6-4a2d-92a5-46e09c868664",
        #                     "order_id": "36bb2437-7402-4794-bf26-4bdf03526439",
        #                     "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9",
        #                     "amount": "1.4",
        #                     "side": "BUY",
        #                     "instrument_code": "BTC_EUR",
        #                     "price": "7341.4",
        #                     "time": "2019-09-27T15:05:32.564Z",
        #                     "sequence": 48670
        #                 }
        #             }
        #         ]
        #     }
        #
        rawTrades = self.safe_value(order, 'trades', [])
        order = self.safe_value(order, 'order', order)
        id = self.safe_string(order, 'order_id')
        clientOrderId = self.safe_string(order, 'client_id')
        timestamp = self.parse8601(self.safe_string(order, 'time'))
        status = self.parse_order_status(self.safe_string(order, 'status'))
        marketId = self.safe_string(order, 'instrument_code')
        symbol = self.safe_symbol(marketId, market, '_')
        price = self.safe_float(order, 'price')
        amount = self.safe_float(order, 'amount')
        cost = None
        filled = self.safe_float(order, 'filled_amount')
        remaining = None
        if filled is not None:
            if amount is not None:
                remaining = max(0, amount - filled)
                if status is None:
                    if remaining > 0:
                        status = 'open'
                    else:
                        status = 'closed'
        side = self.safe_string_lower(order, 'side')
        type = self.safe_string_lower(order, 'type')
        trades = self.parse_trades(rawTrades, market, None, None)
        fees = []
        numTrades = len(trades)
        lastTradeTimestamp = None
        tradeCost = None
        tradeAmount = None
        if numTrades > 0:
            lastTradeTimestamp = trades[0]['timestamp']
            tradeCost = 0
            tradeAmount = 0
            for i in range(0, len(trades)):
                trade = trades[i]
                fees.append(trade['fee'])
                lastTradeTimestamp = max(lastTradeTimestamp, trade['timestamp'])
                tradeCost = self.sum(tradeCost, trade['cost'])
                tradeAmount = self.sum(tradeAmount, trade['amount'])
        average = self.safe_float(order, 'average_price')
        if average is None:
            if (tradeCost is not None) and (tradeAmount is not None) and (tradeAmount != 0):
                average = tradeCost / tradeAmount
        if cost is None:
            if (average is not None) and (filled is not None):
                cost = average * filled
        timeInForce = self.parse_time_in_force(self.safe_string(order, 'time_in_force'))
        stopPrice = self.safe_float(order, 'trigger_price')
        postOnly = self.safe_value(order, 'is_post_only')
        result = {
            'id': id,
            'clientOrderId': clientOrderId,
            'info': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            # 'fee': None,
            'trades': trades,
        }
        numFees = len(fees)
        if numFees > 0:
            if numFees == 1:
                result['fee'] = fees[0]
            else:
                feesByCurrency = self.group_by(fees, 'currency')
                feeCurrencies = list(feesByCurrency.keys())
                numFeesByCurrency = len(feeCurrencies)
                if numFeesByCurrency == 1:
                    feeCurrency = feeCurrencies[0]
                    feeArray = self.safe_value(feesByCurrency, feeCurrency)
                    feeCost = 0
                    for i in range(0, len(feeArray)):
                        feeCost = self.sum(feeCost, feeArray[i]['cost'])
                    result['fee'] = {
                        'cost': feeCost,
                        'currency': feeCurrency,
                    }
                else:
                    result['fees'] = fees
        else:
            result['fee'] = None
        return result

    def parse_time_in_force(self, timeInForce):
        timeInForces = {
            'GOOD_TILL_CANCELLED': 'GTC',
            'GOOD_TILL_TIME': 'GTT',
            'IMMEDIATE_OR_CANCELLED': 'IOC',
            'FILL_OR_KILL': 'FOK',
        }
        return self.safe_string(timeInForces, timeInForce, timeInForce)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        uppercaseType = type.upper()
        request = {
            'instrument_code': market['id'],
            'type': uppercaseType,  # LIMIT, MARKET, STOP
            'side': side.upper(),  # or SELL
            'amount': self.amount_to_precision(symbol, amount),
            # "price": "1234.5678",  # required for LIMIT and STOP orders
            # "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206",  # optional
            # "time_in_force": "GOOD_TILL_CANCELLED",  # limit orders only, GOOD_TILL_CANCELLED, GOOD_TILL_TIME, IMMEDIATE_OR_CANCELLED and FILL_OR_KILL
            # "expire_after": "2020-07-02T19:40:13Z",  # required for GOOD_TILL_TIME
            # "is_post_only": False,  # limit orders only, optional
            # "trigger_price": "1234.5678"  # required for stop orders
        }
        priceIsRequired = False
        if uppercaseType == 'LIMIT' or uppercaseType == 'STOP':
            priceIsRequired = True
        if uppercaseType == 'STOP':
            triggerPrice = self.safe_float(params, 'trigger_price')
            if triggerPrice is None:
                raise ArgumentsRequired(self.id + ' createOrder() requires a trigger_price param for ' + type + ' orders')
            request['trigger_price'] = self.price_to_precision(symbol, triggerPrice)
            params = self.omit(params, 'trigger_price')
        if priceIsRequired:
            request['price'] = self.price_to_precision(symbol, price)
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client_id')
        if clientOrderId is not None:
            request['client_id'] = clientOrderId
            params = self.omit(params, ['clientOrderId', 'client_id'])
        response = self.privatePostAccountOrders(self.extend(request, params))
        #
        #     {
        #         "order_id": "d5492c24-2995-4c18-993a-5b8bf8fffc0d",
        #         "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206",
        #         "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9",
        #         "instrument_code": "BTC_EUR",
        #         "time": "2019-08-01T08:00:44.026Z",
        #         "side": "BUY",
        #         "price": "5000",
        #         "amount": "1",
        #         "filled_amount": "0.5",
        #         "type": "LIMIT",
        #         "time_in_force": "GOOD_TILL_CANCELLED"
        #     }
        #
        return self.parse_order(response, market)

    def cancel_order(self, id, symbol=None, params={}):
        self.load_markets()
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client_id')
        params = self.omit(params, ['clientOrderId', 'client_id'])
        method = 'privateDeleteAccountOrdersOrderId'
        request = {}
        if clientOrderId is not None:
            method = 'privateDeleteAccountOrdersClientClientId'
            request['client_id'] = clientOrderId
        else:
            request['order_id'] = id
        response = getattr(self, method)(self.extend(request, params))
        #
        # responds with an empty body
        #
        return response

    def cancel_all_orders(self, symbol=None, params={}):
        self.load_markets()
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['instrument_code'] = market['id']
        response = self.privateDeleteAccountOrders(self.extend(request, params))
        #
        #     [
        #         "a10e9bd1-8f72-4cfe-9f1b-7f1c8a9bd8ee"
        #     ]
        #
        return response

    def cancel_orders(self, ids, symbol=None, params={}):
        self.load_markets()
        request = {
            'ids': ','.join(ids),
        }
        response = self.privateDeleteAccountOrders(self.extend(request, params))
        #
        #     [
        #         "a10e9bd1-8f72-4cfe-9f1b-7f1c8a9bd8ee"
        #     ]
        #
        return response

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        request = {
            'order_id': id,
        }
        response = self.privateGetAccountOrdersOrderId(self.extend(request, params))
        #
        #     {
        #         "order": {
        #             "order_id": "36bb2437-7402-4794-bf26-4bdf03526439",
        #             "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9",
        #             "time_last_updated": "2019-09-27T15:05:35.096Z",
        #             "sequence": 48782,
        #             "price": "7349.2",
        #             "filled_amount": "100.0",
        #             "status": "FILLED_FULLY",
        #             "amount": "100.0",
        #             "instrument_code": "BTC_EUR",
        #             "side": "BUY",
        #             "time": "2019-09-27T15:05:32.063Z",
        #             "type": "MARKET"
        #         },
        #         "trades": [
        #             {
        #                 "fee": {
        #                     "fee_amount": "0.0014",
        #                     "fee_currency": "BTC",
        #                     "fee_percentage": "0.1",
        #                     "fee_group_id": "default",
        #                     "fee_type": "TAKER",
        #                     "running_trading_volume": "0.0"
        #                 },
        #                 "trade": {
        #                     "trade_id": "fdff2bcc-37d6-4a2d-92a5-46e09c868664",
        #                     "order_id": "36bb2437-7402-4794-bf26-4bdf03526439",
        #                     "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9",
        #                     "amount": "1.4",
        #                     "side": "BUY",
        #                     "instrument_code": "BTC_EUR",
        #                     "price": "7341.4",
        #                     "time": "2019-09-27T15:05:32.564Z",
        #                     "sequence": 48670
        #                 }
        #             }
        #         ]
        #     }
        #
        return self.parse_order(response)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'from': self.iso8601(since),
            # 'to': self.iso8601(self.milliseconds()),  # max range is 100 days
            # 'instrument_code': market['id'],
            # 'with_cancelled_and_rejected': False,  # default is False, orders which have been cancelled by the user before being filled or rejected by the system as invalid, additionally, all inactive filled orders which would return with "with_just_filled_inactive"
            # 'with_just_filled_inactive': False,  # orders which have been filled and are no longer open, use of "with_cancelled_and_rejected" extends "with_just_filled_inactive" and in case both are specified the latter is ignored
            # 'with_just_orders': False,  # do not return any trades corresponsing to the orders, it may be significanly faster and should be used if user is not interesting in trade information
            # 'max_page_size': 100,
            # 'cursor': 'string',  # pointer specifying the position from which the next pages should be returned
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instrument_code'] = market['id']
        if since is not None:
            to = self.safe_string(params, 'to')
            if to is None:
                raise ArgumentsRequired(self.id + ' fetchOrders() requires a "to" iso8601 string param with the since argument is specified, max range is 100 days')
            request['from'] = self.iso8601(since)
        if limit is not None:
            request['max_page_size'] = limit
        response = self.privateGetAccountOrders(self.extend(request, params))
        #
        #     {
        #         "order_history": [
        #             {
        #                 "order": {
        #                     "trigger_price": "12089.88",
        #                     "order_id": "d453ca12-c650-46dd-9dee-66910d96bfc0",
        #                     "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a",
        #                     "instrument_code": "BTC_USDT",
        #                     "time": "2019-08-23T10:02:31.663Z",
        #                     "side": "SELL",
        #                     "price": "10159.76",
        #                     "average_price": "10159.76",
        #                     "amount": "0.2",
        #                     "filled_amount": "0.2",
        #                     "type": "STOP",
        #                     "sequence": 8,
        #                     "status": "FILLED_FULLY"
        #                 },
        #                 "trades": [
        #                     {
        #                         "fee": {
        #                             "fee_amount": "0.4188869",
        #                             "fee_currency": "USDT",
        #                             "fee_percentage": "0.1",
        #                             "fee_group_id": "default",
        #                             "fee_type": "TAKER",
        #                             "running_trading_volume": "0.0"
        #                         },
        #                         "trade": {
        #                             "trade_id": "ec82896f-fd1b-4cbb-89df-a9da85ccbb4b",
        #                             "order_id": "d453ca12-c650-46dd-9dee-66910d96bfc0",
        #                             "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a",
        #                             "amount": "0.2",
        #                             "side": "SELL",
        #                             "instrument_code": "BTC_USDT",
        #                             "price": "10159.76",
        #                             "time": "2019-08-23T10:02:32.663Z",
        #                             "sequence": 9
        #                         }
        #                     }
        #                 ]
        #             },
        #             {
        #                 "order": {
        #                     "order_id": "5151a99e-f414-418f-8cf1-2568d0a63ea5",
        #                     "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a",
        #                     "instrument_code": "BTC_USDT",
        #                     "time": "2019-08-23T10:01:36.773Z",
        #                     "side": "SELL",
        #                     "price": "12289.88",
        #                     "amount": "0.5",
        #                     "filled_amount": "0.0",
        #                     "type": "LIMIT",
        #                     "sequence": 7,
        #                     "status": "OPEN"
        #                 },
        #                 "trades": []
        #             },
        #             {
        #                 "order": {
        #                     "order_id": "ac80d857-75e1-4733-9070-fd4288395fdc",
        #                     "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a",
        #                     "instrument_code": "BTC_USDT",
        #                     "time": "2019-08-23T10:01:25.031Z",
        #                     "side": "SELL",
        #                     "price": "11089.88",
        #                     "amount": "0.1",
        #                     "filled_amount": "0.0",
        #                     "type": "LIMIT",
        #                     "sequence": 6,
        #                     "status": "OPEN"
        #                 },
        #                 "trades": []
        #             }
        #         ],
        #         "max_page_size": 100
        #     }
        #
        orderHistory = self.safe_value(response, 'order_history', [])
        return self.parse_orders(orderHistory, market, since, limit)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'with_cancelled_and_rejected': True,  # default is False, orders which have been cancelled by the user before being filled or rejected by the system as invalid, additionally, all inactive filled orders which would return with "with_just_filled_inactive"
        }
        return self.fetch_open_orders(symbol, since, limit, self.extend(request, params))

    def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            'order_id': id,
            # 'max_page_size': 100,
            # 'cursor': 'string',  # pointer specifying the position from which the next pages should be returned
        }
        if limit is not None:
            request['max_page_size'] = limit
        response = self.privateGetAccountOrdersOrderIdTrades(self.extend(request, params))
        #
        #     {
        #         "trade_history": [
        #             {
        #                 "trade": {
        #                     "trade_id": "2b42efcd-d5b7-4a56-8e12-b69ffd68c5ef",
        #                     "order_id": "66756a10-3e86-48f4-9678-b634c4b135b2",
        #                     "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58",
        #                     "amount": "1234.5678",
        #                     "side": "BUY",
        #                     "instrument_code": "BTC_EUR",
        #                     "price": "1234.5678",
        #                     "time": "2019-08-24T14:15:22Z",
        #                     "price_tick_sequence": 0,
        #                     "sequence": 123456789
        #                 },
        #                 "fee": {
        #                     "fee_amount": "1234.5678",
        #                     "fee_percentage": "1234.5678",
        #                     "fee_group_id": "default",
        #                     "running_trading_volume": "1234.5678",
        #                     "fee_currency": "BTC",
        #                     "fee_type": "TAKER"
        #                 }
        #             }
        #         ],
        #         "max_page_size": 0,
        #         "cursor": "string"
        #     }
        #
        tradeHistory = self.safe_value(response, 'trade_history', [])
        market = None
        if symbol is not None:
            market = self.market(symbol)
        return self.parse_trades(tradeHistory, market, since, limit)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'from': self.iso8601(since),
            # 'to': self.iso8601(self.milliseconds()),  # max range is 100 days
            # 'instrument_code': market['id'],
            # 'max_page_size': 100,
            # 'cursor': 'string',  # pointer specifying the position from which the next pages should be returned
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instrument_code'] = market['id']
        if since is not None:
            to = self.safe_string(params, 'to')
            if to is None:
                raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a "to" iso8601 string param with the since argument is specified, max range is 100 days')
            request['from'] = self.iso8601(since)
        if limit is not None:
            request['max_page_size'] = limit
        response = self.privateGetAccountTrades(self.extend(request, params))
        #
        #     {
        #         "trade_history": [
        #             {
        #                 "trade": {
        #                     "trade_id": "2b42efcd-d5b7-4a56-8e12-b69ffd68c5ef",
        #                     "order_id": "66756a10-3e86-48f4-9678-b634c4b135b2",
        #                     "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58",
        #                     "amount": "1234.5678",
        #                     "side": "BUY",
        #                     "instrument_code": "BTC_EUR",
        #                     "price": "1234.5678",
        #                     "time": "2019-08-24T14:15:22Z",
        #                     "price_tick_sequence": 0,
        #                     "sequence": 123456789
        #                 },
        #                 "fee": {
        #                     "fee_amount": "1234.5678",
        #                     "fee_percentage": "1234.5678",
        #                     "fee_group_id": "default",
        #                     "running_trading_volume": "1234.5678",
        #                     "fee_currency": "BTC",
        #                     "fee_type": "TAKER"
        #                 }
        #             }
        #         ],
        #         "max_page_size": 0,
        #         "cursor": "string"
        #     }
        #
        tradeHistory = self.safe_value(response, 'trade_history', [])
        return self.parse_trades(tradeHistory, market, since, limit)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api] + '/' + self.version + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if api == 'public':
            if query:
                url += '?' + self.urlencode(query)
        elif api == 'private':
            self.check_required_credentials()
            headers = {
                'Accept': 'application/json',
                'Authorization': 'Bearer ' + self.apiKey,
            }
            if method == 'POST':
                body = self.json(query)
                headers['Content-Type'] = 'application/json'
            else:
                if query:
                    url += '?' + self.urlencode(query)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return
        #
        #     {"error":"MISSING_FROM_PARAM"}
        #     {"error":"MISSING_TO_PARAM"}
        #     {"error":"CANDLESTICKS_TIME_RANGE_TOO_BIG"}
        #
        message = self.safe_string(response, 'error')
        if message is not None:
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            raise ExchangeError(feedback)  # unknown message
