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

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

from ccxt.base.exchange import Exchange
import hashlib
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InvalidOrder
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import SIGNIFICANT_DIGITS
from ccxt.base.precise import Precise


class eterbase(Exchange):

    def describe(self):
        return self.deep_extend(super(eterbase, self).describe(), {
            'id': 'eterbase',
            'name': 'Eterbase',
            'countries': ['SK'],  # Slovakia
            'rateLimit': 500,
            'version': 'v1',
            'certified': True,
            'has': {
                'CORS': False,
                'publicAPI': True,
                'privateAPI': True,
                'cancelOrder': True,
                'createOrder': True,
                'deposit': False,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': False,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': False,
                'fetchOrderTrades': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1',
                '5m': '5',
                '15m': '15',
                '1h': '60',
                '4h': '240',
                '1d': '1440',
                '1w': '10080',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/82067900-faeb0f80-96d9-11ea-9f22-0071cfcb9871.jpg',
                'api': 'https://api.eterbase.exchange',
                'www': 'https://www.eterbase.com',
                'doc': 'https://developers.eterbase.exchange',
                'fees': 'https://www.eterbase.com/exchange/fees',
                'referral': 'https://eterbase.exchange/invite/1wjjh4Pe',
            },
            'api': {
                'markets': {
                    'get': [
                        '{id}/order-book',
                    ],
                },
                'public': {
                    'get': [
                        'ping',
                        'assets',
                        'markets',
                        'tickers',
                        'tickers/{id}/ticker',
                        'markets/{id}/trades',
                        'markets/{id}/ohlcv',
                        'wstoken',
                    ],
                },
                'private': {
                    'get': [
                        'accounts/{id}/balances',
                        'accounts/{id}/orders',
                        'accounts/{id}/fills',
                        'orders/{id}/fills',
                        'orders/{id}',
                    ],
                    'post': [
                        'orders',
                        'accounts/{id}/withdrawals',
                    ],
                    'delete': [
                        'orders/{id}',
                    ],
                },
                'feed': {
                    'get': [
                        'feed',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': 0.35 / 100,
                    'maker': 0.35 / 100,
                },
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
                'uid': True,
            },
            'precisionMode': SIGNIFICANT_DIGITS,
            'options': {
                'createMarketBuyOrderRequiresPrice': True,
            },
            'exceptions': {
                'exact': {
                    'Invalid cost': InvalidOrder,  # {"message":"Invalid cost","_links":{"self":{"href":"/orders","templated":false}}}
                    'Invalid order ID': InvalidOrder,  # {"message":"Invalid order ID","_links":{"self":{"href":"/orders/4a151805-d594-4a96-9d64-e3984f2441f7","templated":false}}}
                    'Invalid market !': BadSymbol,  # {"message":"Invalid market !","_links":{"self":{"href":"/markets/300/order-book","templated":false}}}
                },
                'broad': {
                    'Failed to convert argument': BadRequest,
                },
            },
        })

    def fetch_time(self, params={}):
        response = self.publicGetPing(params)
        #
        #     {"pong": 1556354416582}
        #
        return self.safe_integer(response, 'pong')

    def fetch_markets(self, params={}):
        response = self.publicGetMarkets(params)
        #
        #     [
        #         {
        #             "id":33,
        #             "symbol":"ETHUSDT",
        #             "base":"ETH",
        #             "quote":"USDT",
        #             "priceSigDigs":5,
        #             "qtySigDigs":8,
        #             "costSigDigs":8,
        #             "verificationLevelUser":1,
        #             "verificationLevelCorporate":11,
        #             "group":"USD",
        #             "tradingRules":[
        #                 {"attribute":"Qty","condition":"Min","value":0.006},
        #                 {"attribute":"Qty","condition":"Max","value":1000},
        #                 {"attribute":"Cost","condition":"Min","value":1},
        #                 {"attribute":"Cost","condition":"Max","value":210000}
        #             ],
        #             "allowedOrderTypes":[1,2,3,4],
        #             "state":"Trading"
        #         }
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            market = self.parse_market(response[i])
            result.append(market)
        return result

    def parse_market(self, market):
        #
        #     {
        #         "id":33,
        #         "symbol":"ETHUSDT",
        #         "base":"ETH",
        #         "quote":"USDT",
        #         "priceSigDigs":5,
        #         "qtySigDigs":8,
        #         "costSigDigs":8,
        #         "verificationLevelUser":1,
        #         "verificationLevelCorporate":11,
        #         "group":"USD",
        #         "tradingRules":[
        #             {"attribute":"Qty","condition":"Min","value":0.006},
        #             {"attribute":"Qty","condition":"Max","value":1000},
        #             {"attribute":"Cost","condition":"Min","value":1},
        #             {"attribute":"Cost","condition":"Max","value":210000}
        #         ],
        #         "allowedOrderTypes":[1,2,3,4],
        #         "state":"Trading"
        #     }
        #
        id = self.safe_string(market, 'id')
        # numericId = self.safe_string(market, 'id')
        baseId = self.safe_string(market, 'base')
        quoteId = self.safe_string(market, 'quote')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = base + '/' + quote
        state = self.safe_string(market, 'state')
        active = (state == 'Trading')
        precision = {
            'price': self.safe_integer(market, 'priceSigDigs'),
            'amount': self.safe_integer(market, 'qtySigDigs'),
            'cost': self.safe_integer(market, 'costSigDigs'),
        }
        rules = self.safe_value(market, 'tradingRules', [])
        minAmount = None
        maxAmount = None
        minCost = None
        maxCost = None
        for i in range(0, len(rules)):
            rule = rules[i]
            attribute = self.safe_string(rule, 'attribute')
            condition = self.safe_string(rule, 'condition')
            value = self.safe_number(rule, 'value')
            if (attribute == 'Qty') and (condition == 'Min'):
                minAmount = value
            elif (attribute == 'Qty') and (condition == 'Max'):
                maxAmount = value
            elif (attribute == 'Cost') and (condition == 'Min'):
                minCost = value
            elif (attribute == 'Cost') and (condition == 'Max'):
                maxCost = value
        return {
            'id': id,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'baseId': baseId,
            'quoteId': quoteId,
            'info': market,
            'active': active,
            'precision': precision,
            'limits': {
                'amount': {
                    'min': minAmount,
                    'max': maxAmount,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': minCost,
                    'max': maxCost,
                },
            },
        }

    def fetch_currencies(self, params={}):
        response = self.publicGetAssets(params)
        #
        #     [
        #         {
        #             "id":"LINK",
        #             "name":"ChainLink Token",
        #             "precisionDisplay":8,
        #             "precisionMax":18,
        #             "precisionBasis":1000000000000000000,
        #             "precisionStep":1,
        #             "verificationLevelMin":"null",
        #             "cmcId":"LINK",
        #             "txnUrl":"https://etherscan.io/tx/{txnId}",
        #             "state":"Active",
        #             "type":"Crypto",
        #             "isReference":false,
        #             "withdrawalMin":"0",
        #             "withdrawalMax":"50587",
        #             "withdrawalFee":"0.55",
        #             "depositEnabled":true,
        #             "withdrawalEnabled":true,
        #             "description":"",
        #             "coingeckoUrl":"https://www.coingecko.com/en/coins/chainlink",
        #             "coinmarketcapUrl":"https://coinmarketcap.com/currencies/chainlink",
        #             "eterbaseUrl":"https://www.eterbase.com/system-status/LINK",
        #             "explorerUrl":"https://etherscan.io/token/0x514910771af9ca656af840dff83e8264ecf986ca",
        #             "withdrawalMemoAllowed":false,
        #             "countries":[],
        #             "networks":[]
        #         }
        #     ]
        #
        result = {}
        for i in range(0, len(response)):
            currency = response[i]
            id = self.safe_string(currency, 'id')
            precision = self.safe_integer(currency, 'precisionDisplay')
            code = self.safe_currency_code(id)
            depositEnabled = self.safe_value(currency, 'depositEnabled')
            withdrawalEnabled = self.safe_value(currency, 'withdrawalEnabled')
            state = self.safe_string(currency, 'state')
            active = depositEnabled and withdrawalEnabled and (state == 'Active')
            type = self.safe_string_lower(currency, 'type')
            name = self.safe_string(currency, 'name')
            result[code] = {
                'id': id,
                'info': currency,
                'code': code,
                'type': type,
                'name': name,
                'active': active,
                'fee': self.safe_number(currency, 'withdrawalFee'),
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': math.pow(10, -precision),
                        'max': math.pow(10, precision),
                    },
                    'withdraw': {
                        'min': self.safe_number(currency, 'withdrawalMin'),
                        'max': self.safe_number(currency, 'withdrawalMax'),
                    },
                },
            }
        return result

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker
        #
        #     {
        #         "time":1588778516608,
        #         "marketId":250,
        #         "symbol": "ETHUSDT",
        #         "price":0.0,
        #         "change":0.0,
        #         "volumeBase":0.0,
        #         "volume":0.0,
        #         "low":0.0,
        #         "high":0.0,
        #     }
        #
        marketId = self.safe_string(ticker, 'marketId')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.safe_integer(ticker, 'time')
        last = self.safe_number(ticker, 'price')
        baseVolume = self.safe_number(ticker, 'volumeBase')
        quoteVolume = self.safe_number(ticker, 'volume')
        vwap = self.vwap(baseVolume, quoteVolume)
        percentage = self.safe_number(ticker, 'change')
        result = {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_number(ticker, 'high'),
            'low': self.safe_number(ticker, 'low'),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': vwap,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,  # previous day close
            'change': None,
            'percentage': percentage,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }
        return result

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'id': market['id'],
        }
        response = self.publicGetTickersIdTicker(self.extend(request, params))
        #
        #     {
        #         "time":1588778516608,
        #         "marketId":250,
        #         "price":0.0,
        #         "change":0.0,
        #         "volumeBase":0.0,
        #         "volume":0.0,
        #         "low":0.0,
        #         "high":0.0,
        #     }
        #
        return self.parse_ticker(response, market)

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        request = {
            # 'quote': 'USDT',  # identifier of a quote asset to filter the markets
        }
        response = self.publicGetTickers(self.extend(request, params))
        #
        #     [
        #         {
        #             "time":1588831771698,
        #             "marketId":33,
        #             "price":204.54,
        #             "change":-1.03,
        #             "volumeBase":544.9801776699998,
        #             "volume":111550.433735,
        #             "low":200.33,
        #             "high":209.51
        #         },
        #     ]
        #
        return self.parse_tickers(response, symbols)

    def parse_trade(self, trade, market):
        #
        # fetchTrades(public)
        #
        #     {
        #         "id":251199246,
        #         "side":2,
        #         "price":0.022044,
        #         "executedAt":1588830682664,
        #         "qty":0.13545846,
        #         "makerId":"67ed6ef3-33d8-4389-ba70-5c68d9db9f6c",
        #         "takerId":"229ef0d6-fe67-4b5d-9733-824142fab8f3"
        #     }
        #
        # fetchMyTrades, fetchOrderTrades(private)
        #
        #     {
        #         "id": 123,
        #         "marketId": 123,
        #         "side": 1,
        #         "qty": "1.23456",
        #         "price": "1.23456",
        #         "cost": "1.23456",
        #         "fee": "1.23456",
        #         "feeAsset": "XBASE",
        #         "liquidity": 1,
        #         "orderId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        #         "tradeId": 123,
        #         "filledAt": 1556355722341
        #     }
        #
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'qty')
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        fee = None
        feeCost = self.safe_number(trade, 'fee')
        if feeCost is not None:
            feeCurrencyId = self.safe_string(trade, 'feeAsset')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        cost = self.safe_number(trade, 'qty')
        if cost is None:
            cost = self.parse_number(Precise.string_mul(priceString, amountString))
        timestamp = self.safe_integer_2(trade, 'executedAt', 'filledAt')
        tradeSide = self.safe_string(trade, 'side')
        side = 'buy' if (tradeSide == '1') else 'sell'
        liquidity = self.safe_string(trade, 'liquidity')
        takerOrMaker = None
        if liquidity is not None:
            takerOrMaker = 'maker' if (liquidity == '1') else 'taker'
        orderId = self.safe_string(trade, 'orderId')
        id = self.safe_string(trade, 'id')
        marketId = self.safe_string(trade, 'marketId')
        symbol = self.safe_symbol(marketId, market)
        return {
            'info': trade,
            'id': id,
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'id': market['id'],
            # 'offset': 0  # the number of records to skip
        }
        if limit is not None:
            request['limit'] = limit
        response = self.publicGetMarketsIdTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "id":251199246,
        #             "side":2,
        #             "price":0.022044,
        #             "executedAt":1588830682664,
        #             "qty":0.13545846,
        #             "makerId":"67ed6ef3-33d8-4389-ba70-5c68d9db9f6c",
        #             "takerId":"229ef0d6-fe67-4b5d-9733-824142fab8f3"
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        request = {
            'id': self.market_id(symbol),
        }
        response = self.marketsGetIdOrderBook(self.extend(request, params))
        #
        #     {
        #         "type":"ob_snapshot",
        #         "marketId":3,
        #         "timestamp":1588836429847,
        #         "bids":[
        #             [0.021694,8.8793688,1],  # price, amount, count
        #             [0.01937,7.1340473,1],
        #             [0.020774,3.314881,1],
        #         ],
        #         "asks":[
        #             [0.02305,8.8793688,1],
        #             [0.028022,3.314881,1],
        #             [0.022598,3.314881,1],
        #         ]
        #     }
        #
        timestamp = self.safe_integer(response, 'timestamp')
        return self.parse_order_book(response, symbol, timestamp)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "time":1588807500000,
        #         "open":0.022077,
        #         "high":0.022077,
        #         "low":0.022051,
        #         "close":0.022051,
        #         "volume":10.532025119999997
        #     }
        #
        return [
            self.safe_integer(ohlcv, 'time'),
            self.safe_number(ohlcv, 'open'),
            self.safe_number(ohlcv, 'high'),
            self.safe_number(ohlcv, 'low'),
            self.safe_number(ohlcv, 'close'),
            self.safe_number(ohlcv, 'volume'),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        request = {
            # 'id': market['id'],
            'interval': self.timeframes[timeframe],
            # 'start': 1588830682664,  # milliseconds
            # 'end': 1588830682664,  # milliseconds
        }
        duration = self.parse_timeframe(timeframe)
        now = self.milliseconds()
        if since is not None:
            request['start'] = since
            if limit is None:
                request['end'] = now
            else:
                request['end'] = self.sum(since, duration * limit * 1000)
        elif limit is not None:
            request['start'] = now - duration * limit * 1000
            request['end'] = now
        else:
            raise ArgumentsRequired(self.id + ' fetchOHLCV() requires a since argument, or a limit argument, or both')
        self.load_markets()
        market = self.market(symbol)
        request['id'] = market['id']
        response = self.publicGetMarketsIdOhlcv(self.extend(request, params))
        #
        #     [
        #         {"time":1588807500000,"open":0.022077,"high":0.022077,"low":0.022051,"close":0.022051,"volume":10.532025119999997},
        #         {"time":1588807800000,"open":0.022051,"high":0.022051,"low":0.022044,"close":0.022044,"volume":0.655987},
        #         {"time":1588808400000,"open":0.022044,"high":0.022044,"low":0.022044,"close":0.022044,"volume":3.9615545499999993},
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def fetch_balance(self, params={}):
        self.load_markets()
        request = {
            'id': self.uid,
        }
        response = self.privateGetAccountsIdBalances(self.extend(request, params))
        #
        #     [
        #         {
        #             "assetId":"USDT",
        #             "available":"25",
        #             "balance":"25",
        #             "reserved":"0",
        #             "balanceBtc":"0.0",
        #             "balanceRef":"0.0",
        #         }
        #     ]
        #
        result = {'info': response}
        for i in range(0, len(response)):
            balance = response[i]
            currencyId = self.safe_string(balance, 'assetId')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(balance, 'available')
            account['used'] = self.safe_string(balance, 'reserved')
            account['total'] = self.safe_string(balance, 'balance')
            result[code] = account
        return self.parse_balance(result, False)

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        request = {
            'id': id,
        }
        response = self.privateGetOrdersId(self.extend(request, params))
        #
        #     {
        #         "id": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        #         "accountId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        #         "marketId": 123,
        #         "type": 1,
        #         "side": 1,
        #         "qty": "1.23456",
        #         "cost": "1.23456",
        #         "remainingQty": "1.23456",
        #         "remainingCost": "1.23456",
        #         "limitPrice": "1.23456",
        #         "stopPrice": "1.23456",
        #         "postOnly": False,
        #         "timeInForce": "GTC",
        #         "state": 1,
        #         "closeReason": "FILLED",
        #         "placedAt": 1556355722341,
        #         "closedAt": 1556355722341
        #     }
        #
        return self.parse_order(response)

    def parse_order_status(self, status):
        statuses = {
            '1': None,  # pending
            '2': 'open',  # open
            '3': 'open',  # partially filled
            '4': 'closed',  # closed
            'FILLED': 'closed',
            'USER_REQUESTED_CANCEL': 'canceled',
            'ADMINISTRATIVE_CANCEL': 'canceled',
            'NOT_ENOUGH_LIQUIDITY': 'canceled',
            'EXPIRED': 'expired',
            'ONE_CANCELS_OTHER': 'canceled',
        }
        return self.safe_string(statuses, status)

    def parse_order(self, order, market=None):
        #
        # fetchOrder, fetchOpenOrders, fetchClosedOrders
        #
        #     {
        #         "id": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        #         "accountId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        #         "marketId": 123,
        #         "type": 1,
        #         "side": 1,
        #         "qty": "1.23456",
        #         "cost": "1.23456",
        #         "remainingQty": "1.23456",
        #         "remainingCost": "1.23456",
        #         "limitPrice": "1.23456",
        #         "stopPrice": "1.23456",
        #         "postOnly": False,
        #         "timeInForce": "GTC",
        #         "state": 1,
        #         "closeReason": "FILLED",
        #         "placedAt": 1556355722341,
        #         "closedAt": 1556355722341
        #     }
        #
        # createOrder
        #
        #     market buy
        #
        #     {
        #         "id":"ff81127c-8fd5-4846-b683-110639dcd322",
        #         "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2",
        #         "marketId":33,
        #         "type":1,
        #         "side":1,
        #         "cost":"25",
        #         "postOnly":false,
        #         "timeInForce":"GTC",
        #         "state":1,
        #         "placedAt":1589510846735
        #     }
        #
        #     market sell, limit buy, limit sell
        #
        #     {
        #         "id":"042a38b0-e369-4ad2-ae73-a18ff6b1dcf1",
        #         "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2",
        #         "marketId":33,
        #         "type":2,
        #         "side":1,
        #         "qty":"1000",
        #         "limitPrice":"100",
        #         "postOnly":false,
        #         "timeInForce":"GTC",
        #         "state":1,
        #         "placedAt":1589403938682,
        #     }
        #
        id = self.safe_string(order, 'id')
        timestamp = self.safe_integer(order, 'placedAt')
        marketId = self.safe_integer(order, 'marketId')
        symbol = self.safe_symbol(marketId, market)
        status = self.parse_order_status(self.safe_string(order, 'state'))
        if status == 'closed':
            status = self.parse_order_status(self.safe_string(order, 'closeReason'))
        orderSide = self.safe_string(order, 'side')
        side = 'buy' if (orderSide == '1') else 'sell'
        orderType = self.safe_string(order, 'type')
        type = None
        if orderType == '1':
            type = 'market'
        elif orderType == '2':
            type = 'limit'
        elif orderType == '3':
            type = 'stopmarket'
        else:
            type = 'stoplimit'
        price = self.safe_number(order, 'limitPrice')
        amount = self.safe_number(order, 'qty')
        remaining = self.safe_number(order, 'remainingQty')
        filled = None
        remainingCost = self.safe_number(order, 'remainingCost')
        if (remainingCost is not None) and (remainingCost == 0.0):
            remaining = 0
        if (amount is not None) and (remaining is not None):
            filled = max(0, amount - remaining)
        cost = self.safe_number(order, 'cost')
        if type == 'market':
            if price == 0.0:
                if (cost is not None) and (filled is not None):
                    if (cost > 0) and (filled > 0):
                        price = cost / filled
        average = None
        if cost is not None:
            if filled:
                average = cost / filled
        timeInForce = self.safe_string(order, 'timeInForce')
        stopPrice = self.safe_number(order, 'stopPrice')
        postOnly = self.safe_value(order, 'postOnly')
        return {
            'info': order,
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            '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': None,
        }

    def fetch_orders_by_state(self, state, symbol=None, since=None, limit=None, params={}):
        now = self.milliseconds()
        ninetyDays = 90 * 24 * 60 * 60 * 1000  # 90 days timerange max
        request = {
            'id': self.uid,
            'state': state,
            # 'side': Integer,  # 1 = buy, 2 = sell
            # 'offset': 0,  # the number of records to skip
        }
        if since is None:
            request['from'] = now - ninetyDays
            request['to'] = now
        else:
            request['from'] = since
            request['to'] = self.sum(since, ninetyDays)
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['marketId'] = market['id']
        if limit is not None:
            request['limit'] = limit  # default 50
        response = self.privateGetAccountsIdOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        #             "accountId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        #             "marketId": 123,
        #             "type": 1,
        #             "side": 1,
        #             "qty": "1.23456",
        #             "cost": "1.23456",
        #             "remainingQty": "1.23456",
        #             "remainingCost": "1.23456",
        #             "limitPrice": "1.23456",
        #             "stopPrice": "1.23456",
        #             "postOnly": False,
        #             "timeInForce": "GTC",
        #             "state": 1,
        #             "closeReason": "FILLED",
        #             "placedAt": 1556355722341,
        #             "closedAt": 1556355722341
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        return self.fetch_orders_by_state('INACTIVE', symbol, since, limit, params)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        return self.fetch_orders_by_state('ACTIVE', symbol, since, limit, params)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        now = self.milliseconds()
        ninetyDays = 90 * 24 * 60 * 60 * 1000  # 90 days timerange max
        request = {
            'id': self.uid,
            # 'side': Integer,  # 1 = buy, 2 = sell
            # 'offset': 0,  # the number of records to skip
        }
        if since is None:
            request['from'] = now - ninetyDays
            request['to'] = now
        else:
            request['from'] = since
            request['to'] = self.sum(since, ninetyDays)
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['marketId'] = market['id']
        if limit is not None:
            request['limit'] = limit  # default 50, max 200
        response = self.privateGetAccountsIdFills(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": 123,
        #             "marketId": 123,
        #             "side": 1,
        #             "qty": "1.23456",
        #             "price": "1.23456",
        #             "cost": "1.23456",
        #             "fee": "1.23456",
        #             "feeAsset": "XBASE",
        #             "liquidity": 1,
        #             "orderId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        #             "tradeId": 123,
        #             "filledAt": 1556355722341
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            'id': id,
        }
        trades = self.privateGetOrdersIdFills(self.extend(request, params))
        return self.parse_trades(trades)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        uppercaseType = type.upper()
        if uppercaseType == 'MARKET':
            type = 1
        elif uppercaseType == 'LIMIT':
            type = 2
        elif uppercaseType == 'STOPMARKET':
            type = 3
        elif uppercaseType == 'STOPLIMIT':
            type = 4
        uppercaseSide = side.upper()
        side = uppercaseSide == 1 if 'BUY' else 2
        request = {
            'accountId': self.uid,
            'marketId': market['id'],
            'type': type,
            'side': side,
            # 'postOnly': False,
            # 'timeInForce': 'GTC',
        }
        clientOrderId = self.safe_value_2(params, 'refId', 'clientOrderId')
        query = params
        if clientOrderId is not None:
            request['refId'] = clientOrderId
            query = self.omit(params, ['refId', 'clientOrderId'])
        if (uppercaseType == 'MARKET') and (uppercaseSide == 'BUY'):
            # for market buy it requires the amount of quote currency to spend
            cost = self.safe_number(params, 'cost')
            if self.options['createMarketBuyOrderRequiresPrice']:
                if cost is None:
                    if price is not None:
                        cost = amount * price
                    else:
                        raise InvalidOrder(self.id + " createOrder() requires the price argument with market buy orders to calculate total order cost(amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = False to supply the cost in the amount argument(the exchange-specific behaviour)")
            else:
                cost = amount if (cost is None) else cost
            precision = market['precision']['price']
            request['cost'] = self.decimal_to_precision(cost, TRUNCATE, precision, self.precisionMode)
        else:
            request['qty'] = self.amount_to_precision(symbol, amount)
        if uppercaseType == 'LIMIT':
            request['limitPrice'] = self.price_to_precision(symbol, price)
        response = self.privatePostOrders(self.extend(request, query))
        #
        # market buy
        #
        #     {
        #         "id":"ff81127c-8fd5-4846-b683-110639dcd322",
        #         "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2",
        #         "marketId":33,
        #         "type":1,
        #         "side":1,
        #         "cost":"25",
        #         "postOnly":false,
        #         "timeInForce":"GTC",
        #         "state":1,
        #         "placedAt":1589510846735
        #     }
        #
        # market sell, limit buy, limit sell
        #
        #     {
        #         "id":"042a38b0-e369-4ad2-ae73-a18ff6b1dcf1",
        #         "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2",
        #         "marketId":33,
        #         "type":2,
        #         "side":1,
        #         "qty":"1000",
        #         "limitPrice":"100",
        #         "postOnly":false,
        #         "timeInForce":"GTC",
        #         "state":1,
        #         "placedAt":1589403938682,
        #     }
        #
        return self.parse_order(response, market)

    def cancel_order(self, id, symbol=None, params={}):
        request = {
            'id': id,
        }
        return self.privateDeleteOrdersId(self.extend(request, params))

    def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        self.load_markets()
        currency = self.currency(code)
        request = {
            'id': self.uid,
            'accountId': self.uid,
            'assetId': currency['id'],
            'amount': amount,
            # 'cryptoAddress': address,
            # 'accountNumber': 'IBAN',  # IBAN account number
            # 'networkId': 'XBASE',  # underlying network
        }
        if address is not None:
            request['cryptoAddress'] = address
            if tag is not None:
                request['memo'] = tag
        response = self.privatePostAccountsIdWithdrawals(self.extend(request, params))
        #
        #     {
        #         "id": "98b62dde-a87f-45f0-8db8-80ae2d312fa6"
        #     }
        #
        return {
            'info': response,
            'id': self.safe_string(response, 'id'),
        }

    def sign(self, path, api='public', method='GET', params={}, httpHeaders=None, body=None):
        query = self.omit(params, self.extract_params(path))
        request = '/'
        if api == 'public':
            request += 'api/' + self.version
        elif api == 'private':
            request += 'api/' + self.version
        elif api == 'markets':
            request += 'api/' + api
        request += '/' + self.implode_params(path, params)
        if method == 'GET':
            if query:
                request += '?' + self.urlencode(query)
        url = self.urls['api'] + request
        if api == 'private':
            self.check_required_credentials()
            payload = ''
            if method != 'GET':
                if query:
                    body = self.json(query)
                    payload = body
            # construct signature
            hasBody = (method == 'POST') or (method == 'PUT') or (method == 'PATCH')
            # date = 'Mon, 30 Sep 2019 13:57:23 GMT'
            date = self.rfc2616(self.milliseconds())
            headersCSV = 'date' + ' ' + 'request-line'
            message = 'date' + ':' + ' ' + date + "\n" + method + ' ' + request + ' HTTP/1.1'  # eslint-disable-line quotes
            digest = ''
            if hasBody:
                digest = 'SHA-256=' + self.hash(payload, 'sha256', 'base64')
                message += "\ndigest" + ':' + ' ' + digest  # eslint-disable-line quotes
                headersCSV += ' ' + 'digest'
            signature = self.hmac(self.encode(message), self.encode(self.secret), hashlib.sha256, 'base64')
            authorizationHeader = 'hmac username="' + self.apiKey + '",algorithm="hmac-sha256",headers="' + headersCSV + '",' + 'signature="' + signature + '"'
            httpHeaders = {
                'Date': date,
                'Authorization': authorizationHeader,
                'Content-Type': 'application/json',
            }
            if hasBody:
                httpHeaders['Digest'] = digest
        return {'url': url, 'method': method, 'body': body, 'headers': httpHeaders}

    def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return  # fallback to default error handler
        #
        #     {"message":"Invalid cost","_links":{"self":{"href":"/orders","templated":false}}}
        #
        message = self.safe_string(response, 'message')
        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
