# -*- 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
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
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 NotSupported
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable


class timex(Exchange):

    def describe(self):
        return self.deep_extend(super(timex, self).describe(), {
            'id': 'timex',
            'name': 'TimeX',
            'countries': ['AU'],
            'version': 'v1',
            'rateLimit': 1500,
            'has': {
                'cancelOrder': True,
                'cancelOrders': True,
                'CORS': False,
                'createOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': True,  # maker fee only
            },
            'timeframes': {
                '1m': 'I1',
                '5m': 'I5',
                '15m': 'I15',
                '30m': 'I30',
                '1h': 'H1',
                '2h': 'H2',
                '4h': 'H4',
                '6h': 'H6',
                '12h': 'H12',
                '1d': 'D1',
                '1w': 'W1',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/70423869-6839ab00-1a7f-11ea-8f94-13ae72c31115.jpg',
                'api': 'https://plasma-relay-backend.timex.io',
                'www': 'https://timex.io',
                'doc': 'https://docs.timex.io',
                'referral': 'https://timex.io/?refcode=1x27vNkTbP1uwkCck',
            },
            'api': {
                'custody': {
                    'get': [
                        'credentials',  # Get api key for address
                        'credentials/h/{hash}',  # Get api key by hash
                        'credentials/k/{key}',  # Get api key by key
                        'credentials/me/address',  # Get api key by hash
                        'deposit-addresses',  # Get deposit addresses list
                        'deposit-addresses/h/{hash}',  # Get deposit address by hash
                    ],
                },
                'history': {
                    'get': [
                        'orders',  # Gets historical orders
                        'orders/details',  # Gets order details
                        'orders/export/csv',  # Export orders to csv
                        'trades',  # Gets historical trades
                        'trades/export/csv',  # Export trades to csv
                    ],
                },
                'currencies': {
                    'get': [
                        'a/{address}',  # Gets currency by address
                        'i/{id}',  # Gets currency by id
                        's/{symbol}',  # Gets currency by symbol
                    ],
                    'post': [
                        'perform',  # Creates new currency
                        'prepare',  # Prepare creates new currency
                        'remove/perform',  # Removes currency by symbol
                        's/{symbol}/remove/prepare',  # Prepare remove currency by symbol
                        's/{symbol}/update/perform',  # Prepare update currency by symbol
                        's/{symbol}/update/prepare',  # Prepare update currency by symbol
                    ],
                },
                'markets': {
                    'get': [
                        'i/{id}',  # Gets market by id
                        's/{symbol}',  # Gets market by symbol
                    ],
                    'post': [
                        'perform',  # Creates new market
                        'prepare',  # Prepare creates new market
                        'remove/perform',  # Removes market by symbol
                        's/{symbol}/remove/prepare',  # Prepare remove market by symbol
                        's/{symbol}/update/perform',  # Prepare update market by symbol
                        's/{symbol}/update/prepare',  # Prepare update market by symbol
                    ],
                },
                'public': {
                    'get': [
                        'candles',  # Gets candles
                        'currencies',  # Gets all the currencies
                        'markets',  # Gets all the markets
                        'orderbook',  # Gets orderbook
                        'orderbook/raw',  # Gets raw orderbook
                        'orderbook/v2',  # Gets orderbook v2
                        'tickers',  # Gets all the tickers
                        'trades',  # Gets trades
                    ],
                },
                'statistics': {
                    'get': [
                        'address',  # calculateAddressStatistics
                    ],
                },
                'trading': {
                    'get': [
                        'balances',  # Get trading balances for all(or selected) currencies
                        'fees',  # Get trading fee rates for all(or selected) markets
                        'orders',  # Gets open orders
                    ],
                    'post': [
                        'orders',  # Create new order
                        'orders/json',  # Create orders
                    ],
                    'put': [
                        'orders',  # Cancel or update orders
                        'orders/json',  # Update orders
                    ],
                    'delete': [
                        'orders',  # Delete orders
                        'orders/json',  # Delete orders
                    ],
                },
                'tradingview': {
                    'get': [
                        'config',  # Gets config
                        'history',  # Gets history
                        'symbol_info',  # Gets symbol info
                        'time',  # Gets time
                    ],
                },
            },
            'exceptions': {
                'exact': {
                    '0': ExchangeError,
                    '1': NotSupported,
                    '4000': BadRequest,
                    '4001': BadRequest,
                    '4002': InsufficientFunds,
                    '4003': AuthenticationError,
                    '4004': AuthenticationError,
                    '4005': BadRequest,
                    '4006': BadRequest,
                    '4007': BadRequest,
                    '4300': PermissionDenied,
                    '4100': AuthenticationError,
                    '4400': OrderNotFound,
                    '5001': InvalidOrder,
                    '5002': ExchangeError,
                    '400': BadRequest,
                    '401': AuthenticationError,
                    '403': PermissionDenied,
                    '404': OrderNotFound,
                    '429': RateLimitExceeded,
                    '500': ExchangeError,
                    '503': ExchangeNotAvailable,
                },
                'broad': {
                    'Insufficient': InsufficientFunds,
                },
            },
            'options': {
                'fetchTickers': {
                    'period': '1d',
                },
                'fetchTrades': {
                    'sort': 'timestamp,asc',
                },
                'fetchMyTrades': {
                    'sort': 'timestamp,asc',
                },
                'fetchOpenOrders': {
                    'sort': 'createdAt,asc',
                },
                'fetchClosedOrders': {
                    'sort': 'createdAt,asc',
                },
                'defaultSort': 'timestamp,asc',
                'defaultSortOrders': 'createdAt,asc',
            },
        })

    async def fetch_markets(self, params={}):
        response = await self.publicGetMarkets(params)
        #
        #     [
        #         {
        #             "symbol": "ETHBTC",
        #             "name": "ETH/BTC",
        #             "baseCurrency": "ETH",
        #             "baseTokenAddress": "0x45932db54b38af1f5a57136302eeba66a5975c15",
        #             "quoteCurrency": "BTC",
        #             "quoteTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        #             "feeCurrency": "BTC",
        #             "feeTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        #             "quantityIncrement": "0.0000001",
        #             "takerFee": "0.005",
        #             "makerFee": "0.0025",
        #             "tickSize": "0.00000001",
        #             "baseMinSize": "0.0001",
        #             "quoteMinSize": "0.00001",
        #             "locked": False
        #         }
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            result.append(self.parse_market(response[i]))
        return result

    async def fetch_currencies(self, params={}):
        response = await self.publicGetCurrencies(params)
        #
        #     [
        #         {
        #             "symbol": "BTC",
        #             "name": "Bitcoin",
        #             "address": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        #             "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggb3BhY2l0eT0iMC41IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMwIDUzQzQyLjcwMjUgNTMgNTMgNDIuNzAyNSA1MyAzMEM1MyAxNy4yOTc1IDQyLjcwMjUgNyAzMCA3QzE3LjI5NzUgNyA3IDE3LjI5NzUgNyAzMEM3IDQyLjcwMjUgMTcuMjk3NSA1MyAzMCA1M1pNMzAgNTVDNDMuODA3MSA1NSA1NSA0My44MDcxIDU1IDMwQzU1IDE2LjE5MjkgNDMuODA3MSA1IDMwIDVDMTYuMTkyOSA1IDUgMTYuMTkyOSA1IDMwQzUgNDMuODA3MSAxNi4xOTI5IDU1IDMwIDU1WiIvPgo8cGF0aCBkPSJNNDAuOTQyNSAyNi42NTg1QzQxLjQwMDMgMjMuNjExMyAzOS4wNzA1IDIxLjk3MzIgMzUuODg0OCAyMC44ODA0TDM2LjkxODIgMTYuNzUyNkwzNC4zOTUxIDE2LjEyNjRMMzMuMzg5IDIwLjE0NTVDMzIuNzI1OCAxOS45ODA5IDMyLjA0NDUgMTkuODI1NiAzMS4zNjc1IDE5LjY3MTdMMzIuMzgwOCAxNS42MjYyTDI5Ljg1OTEgMTVMMjguODI1IDE5LjEyNjRDMjguMjc2IDE5LjAwMTkgMjcuNzM3IDE4Ljg3ODggMjcuMjEzOSAxOC43NDkzTDI3LjIxNjggMTguNzM2NEwyMy43MzcyIDE3Ljg3MTJMMjMuMDY2IDIwLjU1NDhDMjMuMDY2IDIwLjU1NDggMjQuOTM4IDIwLjk4MjEgMjQuODk4NSAyMS4wMDg1QzI1LjkyMDQgMjEuMjYyNiAyNi4xMDUgMjEuOTM2IDI2LjA3NDEgMjIuNDY5OUwyNC44OTcgMjcuMTcyNEMyNC45Njc1IDI3LjE5MDMgMjUuMDU4NyAyNy4yMTYgMjUuMTU5MyAyNy4yNTYxQzI1LjA3NTMgMjcuMjM1NCAyNC45ODU0IDI3LjIxMjQgMjQuODkyNyAyNy4xOTAzTDIzLjI0MjggMzMuNzc3OEMyMy4xMTc3IDM0LjA4NjkgMjIuODAwOCAzNC41NTA2IDIyLjA4NjUgMzQuMzc0NkMyMi4xMTE3IDM0LjQxMTEgMjAuMjUyNiAzMy45MTg3IDIwLjI1MjYgMzMuOTE4N0wxOSAzNi43OTQ5TDIyLjI4MzQgMzcuNjFDMjIuODk0MiAzNy43NjI0IDIzLjQ5MjggMzcuOTIyIDI0LjA4MjEgMzguMDcyM0wyMy4wMzggNDIuMjQ3NEwyNS41NTgyIDQyLjg3MzZMMjYuNTkyMyAzOC43NDI5QzI3LjI4MDcgMzguOTI5IDI3Ljk0OSAzOS4xMDA3IDI4LjYwMyAzOS4yNjI0TDI3LjU3MjUgNDMuMzczOEwzMC4wOTU2IDQ0TDMxLjEzOTcgMzkuODMyOEMzNS40NDIyIDQwLjY0MzYgMzguNjc3NCA0MC4zMTY2IDQwLjAzOTIgMzYuNDQxNEM0MS4xMzY1IDMzLjMyMTIgMzkuOTg0NiAzMS41MjEzIDM3LjcyMDkgMzAuMzQ3N0MzOS4zNjk0IDI5Ljk2OTEgNDAuNjExMiAyOC44ODkyIDQwLjk0MjUgMjYuNjU4NVYyNi42NTg1Wk0zNS4xNzc3IDM0LjcwODhDMzQuMzk4IDM3LjgyOSAyOS4xMjI2IDM2LjE0MjIgMjcuNDEyMiAzNS43MTkzTDI4Ljc5NzcgMzAuMTg4MUMzMC41MDgxIDMwLjYxMzIgMzUuOTkyNiAzMS40NTQ4IDM1LjE3NzcgMzQuNzA4OFpNMzUuOTU4MSAyNi42MTM0QzM1LjI0NjcgMjkuNDUxNyAzMC44NTU5IDI4LjAwOTcgMjkuNDMxNiAyNy42NTYxTDMwLjY4NzcgMjIuNjM5NUMzMi4xMTIgMjIuOTkzIDM2LjY5OSAyMy42NTI4IDM1Ljk1ODEgMjYuNjEzNFoiLz4KPC9zdmc+Cg==",
        #             "background": "transparent",
        #             "fiatSymbol": "BTC",
        #             "decimals": 8,
        #             "tradeDecimals": 20,
        #             "displayDecimals": 4,
        #             "crypto": True,
        #             "depositEnabled": True,
        #             "withdrawalEnabled": True,
        #             "transferEnabled": True,
        #             "buyEnabled": False,
        #             "purchaseEnabled": False,
        #             "redeemEnabled": False,
        #             "active": True,
        #             "withdrawalFee": "50000000000000000",
        #             "purchaseCommissions": []
        #         },
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            currency = response[i]
            result.append(self.parse_currency(currency))
        return self.index_by(result, 'code')

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        period = self.safe_string(self.options['fetchTickers'], 'period', '1d')
        request = {
            'period': self.timeframes[period],  # I1, I5, I15, I30, H1, H2, H4, H6, H12, D1, W1
        }
        response = await self.publicGetTickers(self.extend(request, params))
        #
        #     [
        #         {
        #             "ask": 0.017,
        #             "bid": 0.016,
        #             "high": 0.019,
        #             "last": 0.017,
        #             "low": 0.015,
        #             "market": "TIME/ETH",
        #             "open": 0.016,
        #             "period": "H1",
        #             "timestamp": "2018-12-14T20:50:36.134Z",
        #             "volume": 4.57,
        #             "volumeQuote": 0.07312
        #         }
        #     ]
        #
        return self.parse_tickers(response, symbols)

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        period = self.safe_string(self.options['fetchTickers'], 'period', '1d')
        request = {
            'market': market['id'],
            'period': self.timeframes[period],  # I1, I5, I15, I30, H1, H2, H4, H6, H12, D1, W1
        }
        response = await self.publicGetTickers(self.extend(request, params))
        #
        #     [
        #         {
        #             "ask": 0.017,
        #             "bid": 0.016,
        #             "high": 0.019,
        #             "last": 0.017,
        #             "low": 0.015,
        #             "market": "TIME/ETH",
        #             "open": 0.016,
        #             "period": "H1",
        #             "timestamp": "2018-12-14T20:50:36.134Z",
        #             "volume": 4.57,
        #             "volumeQuote": 0.07312
        #         }
        #     ]
        #
        ticker = self.safe_value(response, 0)
        return self.parse_ticker(ticker, market)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
        }
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetOrderbookV2(self.extend(request, params))
        #
        #     {
        #         "timestamp":"2019-12-05T00:21:09.538",
        #         "bid":[
        #             {
        #                 "index":"2",
        #                 "price":"0.02024007",
        #                 "baseTokenAmount":"0.0096894",
        #                 "baseTokenCumulativeAmount":"0.0096894",
        #                 "quoteTokenAmount":"0.000196114134258",
        #                 "quoteTokenCumulativeAmount":"0.000196114134258"
        #             },
        #         "ask":[
        #             {
        #                 "index":"-3",
        #                 "price":"0.02024012",
        #                 "baseTokenAmount":"0.005",
        #                 "baseTokenCumulativeAmount":"0.005",
        #                 "quoteTokenAmount":"0.0001012006",
        #                 "quoteTokenCumulativeAmount":"0.0001012006"
        #             },
        #         ]
        #     }
        #
        timestamp = self.parse8601(self.safe_string(response, 'timestamp'))
        return self.parse_order_book(response, timestamp, 'bid', 'ask', 'price', 'baseTokenAmount')

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        options = self.safe_value(self.options, 'fetchTrades', {})
        defaultSort = self.safe_value(options, 'sort', 'timestamp,asc')
        sort = self.safe_string(params, 'sort', defaultSort)
        query = self.omit(params, 'sort')
        request = {
            # 'address': 'string',  # trade’s member account(?)
            # 'cursor': 1234,  # int64(?)
            # 'from': self.iso8601(since),
            'market': market['id'],
            # 'page': 0,  # results page you want to retrieve 0 .. N
            # 'size': limit,  # number of records per page, 100 by default
            'sort': sort,  # array[string], sorting criteria in the format "property,asc" or "property,desc", default is ascending
            # 'till': self.iso8601(self.milliseconds()),
        }
        if since is not None:
            request['from'] = self.iso8601(since)
        if limit is not None:
            request['size'] = limit  # default is 100
        response = await self.publicGetTrades(self.extend(request, query))
        #
        #     [
        #         {
        #             "id":1,
        #             "timestamp":"2019-06-25T17:01:50.309",
        #             "direction":"BUY",
        #             "price":"0.027",
        #             "quantity":"0.001"
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
            'period': self.timeframes[timeframe],
        }
        # if since and limit are not specified
        duration = self.parse_timeframe(timeframe)
        if since is not None:
            request['from'] = self.iso8601(since)
            if limit is not None:
                request['till'] = self.iso8601(self.sum(since, self.sum(limit, 1) * duration * 1000))
        elif limit is not None:
            now = self.milliseconds()
            request['till'] = self.iso8601(now)
            request['from'] = self.iso8601(now - limit * duration * 1000 - 1)
        else:
            request['till'] = self.iso8601(self.milliseconds())
        response = await self.publicGetCandles(self.extend(request, params))
        #
        #     [
        #         {
        #             "timestamp":"2019-12-04T23:00:00",
        #             "open":"0.02024009",
        #             "high":"0.02024009",
        #             "low":"0.02024009",
        #             "close":"0.02024009",
        #             "volume":"0.00008096036",
        #             "volumeQuote":"0.004",
        #         },
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    async def fetch_balance(self, params={}):
        await self.load_markets()
        balances = await self.tradingGetBalances(params)
        #
        #     [
        #         {"currency":"BTC","totalBalance":"0","lockedBalance":"0"},
        #         {"currency":"AUDT","totalBalance":"0","lockedBalance":"0"},
        #         {"currency":"ETH","totalBalance":"0","lockedBalance":"0"},
        #         {"currency":"TIME","totalBalance":"0","lockedBalance":"0"},
        #         {"currency":"USDT","totalBalance":"0","lockedBalance":"0"}
        #     ]
        #
        result = {'info': balances}
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['total'] = self.safe_float(balance, 'totalBalance')
            account['used'] = self.safe_float(balance, 'lockedBalance')
            result[code] = account
        return self.parse_balance(result)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'quantity': self.amount_to_precision(symbol, amount),
            'side': side.upper(),
            # 'clientOrderId': '123',
            # 'expireIn': 1575523308,  # in seconds
            # 'expireTime': 1575523308,  # unix timestamp
        }
        query = params
        if type == 'limit':
            request['price'] = self.price_to_precision(symbol, price)
            defaultExpireIn = self.safe_integer(self.options, 'expireIn')
            expireTime = self.safe_value(params, 'expireTime')
            expireIn = self.safe_value(params, 'expireIn', defaultExpireIn)
            if expireTime is not None:
                request['expireTime'] = expireTime
            elif expireIn is not None:
                request['expireIn'] = expireIn
            else:
                raise InvalidOrder(self.id + ' createOrder() method requires a expireTime or expireIn param for a ' + type + ' order, you can also set the expireIn exchange-wide option')
            query = self.omit(params, ['expireTime', 'expireIn'])
        else:
            request['price'] = 0
        response = await self.tradingPostOrders(self.extend(request, query))
        #
        #     {
        #         "orders": [
        #             {
        #                 "cancelledQuantity": "0.3",
        #                 "clientOrderId": "my-order-1",
        #                 "createdAt": "1970-01-01T00:00:00",
        #                 "cursorId": 50,
        #                 "expireTime": "1970-01-01T00:00:00",
        #                 "filledQuantity": "0.3",
        #                 "id": "string",
        #                 "price": "0.017",
        #                 "quantity": "0.3",
        #                 "side": "BUY",
        #                 "symbol": "TIMEETH",
        #                 "type": "LIMIT",
        #                 "updatedAt": "1970-01-01T00:00:00"
        #             }
        #         ]
        #     }
        #
        orders = self.safe_value(response, 'orders', [])
        order = self.safe_value(orders, 0, {})
        return self.parse_order(order, market)

    async def edit_order(self, id, symbol, type, side, amount=None, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'id': id,
        }
        if amount is not None:
            request['quantity'] = self.amount_to_precision(symbol, amount)
        if price is not None:
            request['price'] = self.price_to_precision(symbol, price)
        response = await self.tradingPutOrders(self.extend(request, params))
        #
        #     {
        #         "changedOrders": [
        #             {
        #                 "newOrder": {
        #                 "cancelledQuantity": "0.3",
        #                 "clientOrderId": "my-order-1",
        #                 "createdAt": "1970-01-01T00:00:00",
        #                 "cursorId": 50,
        #                 "expireTime": "1970-01-01T00:00:00",
        #                 "filledQuantity": "0.3",
        #                 "id": "string",
        #                 "price": "0.017",
        #                 "quantity": "0.3",
        #                 "side": "BUY",
        #                 "symbol": "TIMEETH",
        #                 "type": "LIMIT",
        #                 "updatedAt": "1970-01-01T00:00:00"
        #                 },
        #                 "oldId": "string",
        #             },
        #         ],
        #         "unchangedOrders": ["string"],
        #     }
        #
        if 'unchangedOrders' in response:
            orderIds = self.safe_value(response, 'unchangedOrders', [])
            orderId = self.safe_string(orderIds, 0)
            return {
                'id': orderId,
                'info': response,
            }
        orders = self.safe_value(response, 'changedOrders', [])
        firstOrder = self.safe_value(orders, 0, {})
        order = self.safe_value(firstOrder, 'newOrder', {})
        return self.parse_order(order, market)

    async def cancel_order(self, id, symbol=None, params={}):
        await self.load_markets()
        return await self.cancel_orders([id], symbol, params)

    async def cancel_orders(self, ids, symbol=None, params={}):
        await self.load_markets()
        request = {
            'id': ids,
        }
        response = await self.tradingDeleteOrders(self.extend(request, params))
        #
        #     {
        #         "changedOrders": [
        #             {
        #                 "newOrder": {
        #                     "cancelledQuantity": "0.3",
        #                     "clientOrderId": "my-order-1",
        #                     "createdAt": "1970-01-01T00:00:00",
        #                     "cursorId": 50,
        #                     "expireTime": "1970-01-01T00:00:00",
        #                     "filledQuantity": "0.3",
        #                     "id": "string",
        #                     "price": "0.017",
        #                     "quantity": "0.3",
        #                     "side": "BUY",
        #                     "symbol": "TIMEETH",
        #                     "type": "LIMIT",
        #                     "updatedAt": "1970-01-01T00:00:00"
        #                 },
        #                 "oldId": "string",
        #             },
        #         ],
        #         "unchangedOrders": ["string"],
        #     }
        return response

    async def fetch_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'orderHash': id,
        }
        response = await self.historyGetOrdersDetails(request)
        #
        #     {
        #         "order": {
        #             "cancelledQuantity": "0.3",
        #             "clientOrderId": "my-order-1",
        #             "createdAt": "1970-01-01T00:00:00",
        #             "cursorId": 50,
        #             "expireTime": "1970-01-01T00:00:00",
        #             "filledQuantity": "0.3",
        #             "id": "string",
        #             "price": "0.017",
        #             "quantity": "0.3",
        #             "side": "BUY",
        #             "symbol": "TIMEETH",
        #             "type": "LIMIT",
        #             "updatedAt": "1970-01-01T00:00:00"
        #         },
        #         "trades": [
        #             {
        #                 "fee": "0.3",
        #                 "id": 100,
        #                 "makerOrTaker": "MAKER",
        #                 "makerOrderId": "string",
        #                 "price": "0.017",
        #                 "quantity": "0.3",
        #                 "side": "BUY",
        #                 "symbol": "TIMEETH",
        #                 "takerOrderId": "string",
        #                 "timestamp": "2019-12-05T07:48:26.310Z"
        #             }
        #         ]
        #     }
        #
        order = self.safe_value(response, 'order', {})
        trades = self.safe_value(response, 'trades', [])
        return self.parse_order(self.extend(order, {'trades': trades}))

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        options = self.safe_value(self.options, 'fetchOpenOrders', {})
        defaultSort = self.safe_value(options, 'sort', 'createdAt,asc')
        sort = self.safe_string(params, 'sort', defaultSort)
        query = self.omit(params, 'sort')
        request = {
            # 'clientOrderId': '123',  # order’s client id list for filter
            # page: 0,  # results page you want to retrieve(0 .. N)
            'sort': sort,  # sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple sort criteria are supported
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if limit is not None:
            request['size'] = limit
        response = await self.tradingGetOrders(self.extend(request, query))
        #
        #     {
        #         "orders": [
        #             {
        #                 "cancelledQuantity": "0.3",
        #                 "clientOrderId": "my-order-1",
        #                 "createdAt": "1970-01-01T00:00:00",
        #                 "cursorId": 50,
        #                 "expireTime": "1970-01-01T00:00:00",
        #                 "filledQuantity": "0.3",
        #                 "id": "string",
        #                 "price": "0.017",
        #                 "quantity": "0.3",
        #                 "side": "BUY",
        #                 "symbol": "TIMEETH",
        #                 "type": "LIMIT",
        #                 "updatedAt": "1970-01-01T00:00:00"
        #             }
        #         ]
        #     }
        #
        orders = self.safe_value(response, 'orders', [])
        return self.parse_orders(orders, market, since, limit)

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        options = self.safe_value(self.options, 'fetchClosedOrders', {})
        defaultSort = self.safe_value(options, 'sort', 'createdAt,asc')
        sort = self.safe_string(params, 'sort', defaultSort)
        query = self.omit(params, 'sort')
        request = {
            # 'clientOrderId': '123',  # order’s client id list for filter
            # page: 0,  # results page you want to retrieve(0 .. N)
            'sort': sort,  # sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple sort criteria are supported
            'side': 'BUY',  # or 'SELL'
            # 'till': self.iso8601(self.milliseconds()),
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if since is not None:
            request['from'] = self.iso8601(since)
        if limit is not None:
            request['size'] = limit
        response = await self.historyGetOrders(self.extend(request, query))
        #
        #     {
        #         "orders": [
        #             {
        #                 "cancelledQuantity": "0.3",
        #                 "clientOrderId": "my-order-1",
        #                 "createdAt": "1970-01-01T00:00:00",
        #                 "cursorId": 50,
        #                 "expireTime": "1970-01-01T00:00:00",
        #                 "filledQuantity": "0.3",
        #                 "id": "string",
        #                 "price": "0.017",
        #                 "quantity": "0.3",
        #                 "side": "BUY",
        #                 "symbol": "TIMEETH",
        #                 "type": "LIMIT",
        #                 "updatedAt": "1970-01-01T00:00:00"
        #             }
        #         ]
        #     }
        #
        orders = self.safe_value(response, 'orders', [])
        return self.parse_orders(orders, market, since, limit)

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        options = self.safe_value(self.options, 'fetchMyTrades', {})
        defaultSort = self.safe_value(options, 'sort', 'timestamp,asc')
        sort = self.safe_string(params, 'sort', defaultSort)
        query = self.omit(params, 'sort')
        request = {
            # 'cursorId': 123,  # int64(?)
            # 'from': self.iso8601(since),
            # 'makerOrderId': '1234',  # maker order hash
            # 'owner': '...',  # owner address(?)
            # 'page': 0,  # results page you want to retrieve(0 .. N)
            # 'side': 'BUY',  # or 'SELL'
            # 'size': limit,
            'sort': sort,  # sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple sort criteria are supported
            # 'symbol': market['id'],
            # 'takerOrderId': '1234',
            # 'till': self.iso8601(self.milliseconds()),
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if since is not None:
            request['from'] = self.iso8601(since)
        if limit is not None:
            request['size'] = limit
        response = await self.historyGetTrades(self.extend(request, query))
        #
        #     {
        #         "trades": [
        #             {
        #                 "fee": "0.3",
        #                 "id": 100,
        #                 "makerOrTaker": "MAKER",
        #                 "makerOrderId": "string",
        #                 "price": "0.017",
        #                 "quantity": "0.3",
        #                 "side": "BUY",
        #                 "symbol": "TIMEETH",
        #                 "takerOrderId": "string",
        #                 "timestamp": "2019-12-08T04:54:11.171Z"
        #             }
        #         ]
        #     }
        #
        trades = self.safe_value(response, 'trades', [])
        return self.parse_trades(trades, market, since, limit)

    async def fetch_trading_fee(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'markets': market['id'],
        }
        response = await self.tradingGetFees(self.extend(request, params))
        #
        #     [
        #         {
        #             "fee": 0.0075,
        #             "market": "ETHBTC"
        #         }
        #     ]
        #
        result = self.safe_value(response, 0, {})
        return {
            'info': response,
            'maker': self.safe_float(result, 'fee'),
            'taker': None,
        }

    def parse_market(self, market):
        #
        #     {
        #         "symbol": "ETHBTC",
        #         "name": "ETH/BTC",
        #         "baseCurrency": "ETH",
        #         "baseTokenAddress": "0x45932db54b38af1f5a57136302eeba66a5975c15",
        #         "quoteCurrency": "BTC",
        #         "quoteTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        #         "feeCurrency": "BTC",
        #         "feeTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        #         "quantityIncrement": "0.0000001",
        #         "takerFee": "0.005",
        #         "makerFee": "0.0025",
        #         "tickSize": "0.00000001",
        #         "baseMinSize": "0.0001",
        #         "quoteMinSize": "0.00001",
        #         "locked": False
        #     }
        #
        locked = self.safe_value(market, 'locked')
        active = not locked
        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
        precision = {
            'amount': self.precision_from_string(self.safe_string(market, 'quantityIncrement')),
            'price': self.precision_from_string(self.safe_string(market, 'tickSize')),
        }
        amountIncrement = self.safe_float(market, 'quantityIncrement')
        minBase = self.safe_float(market, 'baseMinSize')
        minAmount = max(amountIncrement, minBase)
        priceIncrement = self.safe_float(market, 'tickSize')
        minCost = self.safe_float(market, 'quoteMinSize')
        limits = {
            'amount': {'min': minAmount, 'max': None},
            'price': {'min': priceIncrement, 'max': None},
            'cost': {'min': max(minCost, minAmount * priceIncrement), 'max': None},
        }
        taker = self.safe_float(market, 'takerFee')
        maker = self.safe_float(market, 'makerFee')
        return {
            'id': id,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'baseId': baseId,
            'quoteId': quoteId,
            'type': 'spot',
            'active': active,
            'precision': precision,
            'limits': limits,
            'taker': taker,
            'maker': maker,
            'info': market,
        }

    def parse_currency(self, currency):
        #
        #     {
        #         "symbol": "BTC",
        #         "name": "Bitcoin",
        #         "address": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        #         "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR...mc+Cg==",
        #         "background": "transparent",
        #         "fiatSymbol": "BTC",
        #         "decimals": 8,
        #         "tradeDecimals": 20,
        #         "displayDecimals": 4,
        #         "crypto": True,
        #         "depositEnabled": True,
        #         "withdrawalEnabled": True,
        #         "transferEnabled": True,
        #         "buyEnabled": False,
        #         "purchaseEnabled": False,
        #         "redeemEnabled": False,
        #         "active": True,
        #         "withdrawalFee": "50000000000000000",
        #         "purchaseCommissions": []
        #     }
        #
        # https://github.com/ccxt/ccxt/issues/6878
        #
        #     {
        #         "symbol":"XRP",
        #         "name":"Ripple",
        #         "address":"0x0dc8882914f3ddeebf4cec6dc20edb99df3def6c",
        #         "decimals":6,
        #         "tradeDecimals":16,
        #         "depositEnabled":true,
        #         "withdrawalEnabled":true,
        #         "transferEnabled":true,
        #         "active":true
        #     }
        #
        id = self.safe_string(currency, 'symbol')
        code = self.safe_currency_code(id)
        name = self.safe_string(currency, 'name')
        precision = self.safe_integer(currency, 'decimals')
        active = self.safe_value(currency, 'active')
        # fee = self.safe_float(currency, 'withdrawalFee')
        feeString = self.safe_string(currency, 'withdrawalFee')
        tradeDecimals = self.safe_integer(currency, 'tradeDecimals')
        fee = None
        if (feeString is not None) and (tradeDecimals is not None):
            feeStringLen = len(feeString)
            dotIndex = feeStringLen - tradeDecimals
            if dotIndex > 0:
                whole = feeString[0:dotIndex]
                fraction = feeString[-dotIndex:]
                fee = float(whole + '.' + fraction)
            else:
                fraction = '.'
                for i in range(0, -dotIndex):
                    fraction += '0'
                fee = float(fraction + feeString)
        return {
            'id': code,
            'code': code,
            'info': currency,
            'type': None,
            'name': name,
            'active': active,
            'fee': fee,
            'precision': precision,
            'limits': {
                'withdraw': {'min': fee, 'max': None},
                'amount': {'min': None, 'max': None},
                'price': {'min': None, 'max': None},
                'cost': {'min': None, 'max': None},
            },
        }

    def parse_tickers(self, rawTickers, symbols=None):
        tickers = []
        for i in range(0, len(rawTickers)):
            tickers.append(self.parse_ticker(rawTickers[i]))
        return self.filter_by_array(tickers, 'symbol', symbols)

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         "ask": 0.017,
        #         "bid": 0.016,
        #         "high": 0.019,
        #         "last": 0.017,
        #         "low": 0.015,
        #         "market": "TIME/ETH",
        #         "open": 0.016,
        #         "period": "H1",
        #         "timestamp": "2018-12-14T20:50:36.134Z",
        #         "volume": 4.57,
        #         "volumeQuote": 0.07312
        #     }
        #
        marketId = self.safe_string(ticker, 'market')
        symbol = self.safe_symbol(marketId, market, '/')
        timestamp = self.parse8601(self.safe_string(ticker, 'timestamp'))
        last = self.safe_float(ticker, 'last')
        open = self.safe_float(ticker, 'open')
        change = None
        average = None
        if last is not None and open is not None:
            change = last - open
            average = self.sum(last, open) / 2
        percentage = None
        if change is not None and open:
            percentage = (change / open) * 100
        return {
            'symbol': symbol,
            'info': ticker,
            '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': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': self.safe_float(ticker, 'volume'),
            'quoteVolume': self.safe_float(ticker, 'volumeQuote'),
        }

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         "id":1,
        #         "timestamp":"2019-06-25T17:01:50.309",
        #         "direction":"BUY",
        #         "price":"0.027",
        #         "quantity":"0.001"
        #     }
        #
        # fetchMyTrades, fetchOrder(private)
        #
        #     {
        #         "fee": "0.3",
        #         "id": 100,
        #         "makerOrTaker": "MAKER",
        #         "makerOrderId": "string",
        #         "price": "0.017",
        #         "quantity": "0.3",
        #         "side": "BUY",
        #         "symbol": "TIMEETH",
        #         "takerOrderId": "string",
        #         "timestamp": "2019-12-08T04:54:11.171Z"
        #     }
        #
        marketId = self.safe_string(trade, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.parse8601(self.safe_string(trade, 'timestamp'))
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'quantity')
        id = self.safe_string(trade, 'id')
        side = self.safe_string_lower_2(trade, 'direction', 'side')
        takerOrMaker = self.safe_string_lower(trade, 'makerOrTaker')
        orderId = None
        if takerOrMaker is not None:
            orderId = self.safe_string(trade, takerOrMaker + 'OrderId')
        fee = None
        feeCost = self.safe_float(trade, 'fee')
        if feeCost is not None:
            feeCurrency = None if (market is None) else market['quote']
            fee = {
                'cost': feeCost,
                'currency': feeCurrency,
            }
        cost = None
        if (price is not None) and (amount is not None):
            cost = self.cost_to_precision(symbol, amount * price)
        return {
            'info': trade,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': None,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'takerOrMaker': takerOrMaker,
            'fee': fee,
        }

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "timestamp":"2019-12-04T23:00:00",
        #         "open":"0.02024009",
        #         "high":"0.02024009",
        #         "low":"0.02024009",
        #         "close":"0.02024009",
        #         "volume":"0.00008096036",
        #         "volumeQuote":"0.004",
        #     }
        #
        return [
            self.parse8601(self.safe_string(ohlcv, 'timestamp')),
            self.safe_float(ohlcv, 'open'),
            self.safe_float(ohlcv, 'high'),
            self.safe_float(ohlcv, 'low'),
            self.safe_float(ohlcv, 'close'),
            self.safe_float(ohlcv, 'volume'),
        ]

    def parse_order(self, order, market=None):
        #
        # fetchOrder, createOrder, cancelOrder, cancelOrders, fetchOpenOrders, fetchClosedOrders
        #
        #     {
        #         "cancelledQuantity": "0.3",
        #         "clientOrderId": "my-order-1",
        #         "createdAt": "1970-01-01T00:00:00",
        #         "cursorId": 50,
        #         "expireTime": "1970-01-01T00:00:00",
        #         "filledQuantity": "0.3",
        #         "id": "string",
        #         "price": "0.017",
        #         "quantity": "0.3",
        #         "side": "BUY",
        #         "symbol": "TIMEETH",
        #         "type": "LIMIT",
        #         "updatedAt": "1970-01-01T00:00:00"
        #         "trades": [],  # injected from the outside
        #     }
        #
        id = self.safe_string(order, 'id')
        type = self.safe_string_lower(order, 'type')
        side = self.safe_string_lower(order, 'side')
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.parse8601(self.safe_string(order, 'createdAt'))
        price = self.safe_float(order, 'price')
        amount = self.safe_float(order, 'quantity')
        filled = self.safe_float(order, 'filledQuantity')
        canceledQuantity = self.safe_float(order, 'cancelledQuantity')
        remaining = None
        status = None
        if (amount is not None) and (filled is not None):
            remaining = max(amount - filled, 0.0)
            if filled >= amount:
                status = 'closed'
            elif (canceledQuantity is not None) and (canceledQuantity > 0):
                status = 'canceled'
            else:
                status = 'open'
        cost = float(self.cost_to_precision(symbol, price * filled))
        fee = None
        lastTradeTimestamp = None
        trades = None
        rawTrades = self.safe_value(order, 'trades')
        if rawTrades is not None:
            trades = self.parse_trades(rawTrades, market, None, None, {
                'order': id,
            })
        if trades is not None:
            numTrades = len(trades)
            if numTrades > 0:
                lastTradeTimestamp = trades[numTrades - 1]['timestamp']
        clientOrderId = self.safe_string(order, 'clientOrderId')
        return {
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'amount': amount,
            'cost': cost,
            'average': None,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
            'trades': trades,
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'] + '/' + api + '/' + path
        if params:
            url += '?' + self.urlencode_with_array_repeat(params)
        if api != 'public':
            self.check_required_credentials()
            auth = self.string_to_base64(self.apiKey + ':' + self.secret)
            secret = 'Basic ' + self.decode(auth)
            headers = {'authorization': secret}
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody):
        if response is None:
            return
        if statusCode >= 400:
            #
            #     {"error":{"timestamp":"05.12.2019T05:25:43.584+0000","status":"BAD_REQUEST","message":"Insufficient ETH balance. Required: 1, actual: 0.","code":4001}}
            #     {"error":{"timestamp":"05.12.2019T04:03:25.419+0000","status":"FORBIDDEN","message":"Access denied","code":4300}}
            #
            feedback = self.id + ' ' + responseBody
            error = self.safe_value(response, 'error')
            if error is None:
                error = response
            code = self.safe_string_2(error, 'code', 'status')
            message = self.safe_string_2(error, 'message', 'debugMessage')
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            raise ExchangeError(feedback)
