# -*- 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

# -----------------------------------------------------------------------------

try:
    basestring  # Python 3
except NameError:
    basestring = str  # Python 2
import json
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import NullResponse
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 DDoSProtection
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import InvalidNonce


class cex(Exchange):

    def describe(self):
        return self.deep_extend(super(cex, self).describe(), {
            'id': 'cex',
            'name': 'CEX.IO',
            'countries': ['GB', 'EU', 'CY', 'RU'],
            'rateLimit': 1500,
            'has': {
                'cancelOrder': True,
                'CORS': False,
                'createOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchMarkets': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
            },
            'timeframes': {
                '1m': '1m',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766442-8ddc33b0-5ed8-11e7-8b98-f786aef0f3c9.jpg',
                'api': 'https://cex.io/api',
                'www': 'https://cex.io',
                'doc': 'https://cex.io/cex-api',
                'fees': [
                    'https://cex.io/fee-schedule',
                    'https://cex.io/limits-commissions',
                ],
                'referral': 'https://cex.io/r/0/up105393824/0/',
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
                'uid': True,
            },
            'api': {
                'public': {
                    'get': [
                        'currency_profile',
                        'currency_limits/',
                        'last_price/{pair}/',
                        'last_prices/{currencies}/',
                        'ohlcv/hd/{yyyymmdd}/{pair}',
                        'order_book/{pair}/',
                        'ticker/{pair}/',
                        'tickers/{currencies}/',
                        'trade_history/{pair}/',
                    ],
                    'post': [
                        'convert/{pair}',
                        'price_stats/{pair}',
                    ],
                },
                'private': {
                    'post': [
                        'active_orders_status/',
                        'archived_orders/{pair}/',
                        'balance/',
                        'cancel_order/',
                        'cancel_orders/{pair}/',
                        'cancel_replace_order/{pair}/',
                        'close_position/{pair}/',
                        'get_address/',
                        'get_myfee/',
                        'get_order/',
                        'get_order_tx/',
                        'open_orders/{pair}/',
                        'open_orders/',
                        'open_position/{pair}/',
                        'open_positions/{pair}/',
                        'place_order/{pair}/',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.16 / 100,
                    'taker': 0.25 / 100,
                },
                'funding': {
                    'withdraw': {
                        # 'USD': None,
                        # 'EUR': None,
                        # 'RUB': None,
                        # 'GBP': None,
                        'BTC': 0.001,
                        'ETH': 0.01,
                        'BCH': 0.001,
                        'DASH': 0.01,
                        'BTG': 0.001,
                        'ZEC': 0.001,
                        'XRP': 0.02,
                    },
                    'deposit': {
                        # 'USD': amount => amount * 0.035 + 0.25,
                        # 'EUR': amount => amount * 0.035 + 0.24,
                        # 'RUB': amount => amount * 0.05 + 15.57,
                        # 'GBP': amount => amount * 0.035 + 0.2,
                        'BTC': 0.0,
                        'ETH': 0.0,
                        'BCH': 0.0,
                        'DASH': 0.0,
                        'BTG': 0.0,
                        'ZEC': 0.0,
                        'XRP': 0.0,
                        'XLM': 0.0,
                    },
                },
            },
            'exceptions': {
                'exact': {},
                'broad': {
                    'Insufficient funds': InsufficientFunds,
                    'Nonce must be incremented': InvalidNonce,
                    'Invalid Order': InvalidOrder,
                    'Order not found': OrderNotFound,
                    'limit exceeded': RateLimitExceeded,  # {"error":"rate limit exceeded"}
                    'Invalid API key': AuthenticationError,
                    'There was an error while placing your order': InvalidOrder,
                    'Sorry, too many clients already': DDoSProtection,
                },
            },
            'options': {
                'fetchOHLCVWarning': True,
                'createMarketBuyOrderRequiresPrice': True,
                'order': {
                    'status': {
                        'c': 'canceled',
                        'd': 'closed',
                        'cd': 'canceled',
                        'a': 'open',
                    },
                },
            },
        })

    def fetch_currencies_from_cache(self, params={}):
        # self method is now redundant
        # currencies are now fetched before markets
        options = self.safe_value(self.options, 'fetchCurrencies', {})
        timestamp = self.safe_integer(options, 'timestamp')
        expires = self.safe_integer(options, 'expires', 1000)
        now = self.milliseconds()
        if (timestamp is None) or ((now - timestamp) > expires):
            response = self.publicGetCurrencyProfile(params)
            self.options['fetchCurrencies'] = self.extend(options, {
                'response': response,
                'timestamp': now,
            })
        return self.safe_value(self.options['fetchCurrencies'], 'response')

    def fetch_currencies(self, params={}):
        response = self.fetch_currencies_from_cache(params)
        self.options['currencies'] = {
            'timestamp': self.milliseconds(),
            'response': response,
        }
        #
        #     {
        #         "e":"currency_profile",
        #         "ok":"ok",
        #         "data":{
        #             "symbols":[
        #                 {
        #                     "code":"GHS",
        #                     "contract":true,
        #                     "commodity":true,
        #                     "fiat":false,
        #                     "description":"CEX.IO doesn't provide cloud mining services anymore.",
        #                     "precision":8,
        #                     "scale":0,
        #                     "minimumCurrencyAmount":"0.00000001",
        #                     "minimalWithdrawalAmount":-1
        #                 },
        #                 {
        #                     "code":"BTC",
        #                     "contract":false,
        #                     "commodity":false,
        #                     "fiat":false,
        #                     "description":"",
        #                     "precision":8,
        #                     "scale":0,
        #                     "minimumCurrencyAmount":"0.00000001",
        #                     "minimalWithdrawalAmount":0.002
        #                 },
        #                 {
        #                     "code":"ETH",
        #                     "contract":false,
        #                     "commodity":false,
        #                     "fiat":false,
        #                     "description":"",
        #                     "precision":8,
        #                     "scale":2,
        #                     "minimumCurrencyAmount":"0.00000100",
        #                     "minimalWithdrawalAmount":0.01
        #                 }
        #             ],
        #             "pairs":[
        #                 {
        #                     "symbol1":"BTC",
        #                     "symbol2":"USD",
        #                     "pricePrecision":1,
        #                     "priceScale":"/1000000",
        #                     "minLotSize":0.002,
        #                     "minLotSizeS2":20
        #                 },
        #                 {
        #                     "symbol1":"ETH",
        #                     "symbol2":"USD",
        #                     "pricePrecision":2,
        #                     "priceScale":"/10000",
        #                     "minLotSize":0.1,
        #                     "minLotSizeS2":20
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', [])
        currencies = self.safe_value(data, 'symbols', [])
        result = {}
        for i in range(0, len(currencies)):
            currency = currencies[i]
            id = self.safe_string(currency, 'code')
            code = self.safe_currency_code(id)
            precision = self.safe_integer(currency, 'precision')
            active = True
            result[code] = {
                'id': id,
                'code': code,
                'name': id,
                'active': active,
                'precision': precision,
                'fee': None,
                'limits': {
                    'amount': {
                        'min': self.safe_float(currency, 'minimumCurrencyAmount'),
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_float(currency, 'minimalWithdrawalAmount'),
                        'max': None,
                    },
                },
                'info': currency,
            }
        return result

    def fetch_markets(self, params={}):
        currenciesResponse = self.fetch_currencies_from_cache(params)
        currenciesData = self.safe_value(currenciesResponse, 'data', {})
        currencies = self.safe_value(currenciesData, 'symbols', [])
        currenciesById = self.index_by(currencies, 'code')
        pairs = self.safe_value(currenciesData, 'pairs', [])
        response = self.publicGetCurrencyLimits(params)
        #
        #     {
        #         "e":"currency_limits",
        #         "ok":"ok",
        #         "data": {
        #             "pairs":[
        #                 {
        #                     "symbol1":"BTC",
        #                     "symbol2":"USD",
        #                     "minLotSize":0.002,
        #                     "minLotSizeS2":20,
        #                     "maxLotSize":30,
        #                     "minPrice":"1500",
        #                     "maxPrice":"35000"
        #                 },
        #                 {
        #                     "symbol1":"BCH",
        #                     "symbol2":"EUR",
        #                     "minLotSize":0.1,
        #                     "minLotSizeS2":20,
        #                     "maxLotSize":null,
        #                     "minPrice":"25",
        #                     "maxPrice":"8192"
        #                 }
        #             ]
        #         }
        #     }
        #
        result = []
        markets = self.safe_value(response['data'], 'pairs')
        for i in range(0, len(markets)):
            market = markets[i]
            baseId = self.safe_string(market, 'symbol1')
            quoteId = self.safe_string(market, 'symbol2')
            id = baseId + '/' + quoteId
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            baseCurrency = self.safe_value(currenciesById, baseId, {})
            quoteCurrency = self.safe_value(currenciesById, quoteId, {})
            pricePrecision = self.safe_integer(quoteCurrency, 'precision', 8)
            for j in range(0, len(pairs)):
                pair = pairs[j]
                if (pair['symbol1'] == baseId) and (pair['symbol2'] == quoteId):
                    # we might need to account for `priceScale` here
                    pricePrecision = self.safe_integer(pair, 'pricePrecision', pricePrecision)
            baseCcyPrecision = self.safe_integer(baseCurrency, 'precision', 8)
            baseCcyScale = self.safe_integer(baseCurrency, 'scale', 0)
            amountPrecision = baseCcyPrecision - baseCcyScale
            precision = {
                'amount': amountPrecision,
                'price': pricePrecision,
            }
            result.append({
                'id': id,
                'info': market,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': self.safe_float(market, 'minLotSize'),
                        'max': self.safe_float(market, 'maxLotSize'),
                    },
                    'price': {
                        'min': self.safe_float(market, 'minPrice'),
                        'max': self.safe_float(market, 'maxPrice'),
                    },
                    'cost': {
                        'min': self.safe_float(market, 'minLotSizeS2'),
                        'max': None,
                    },
                },
                'active': None,
            })
        return result

    def fetch_balance(self, params={}):
        self.load_markets()
        response = self.privatePostBalance(params)
        result = {'info': response}
        ommited = ['username', 'timestamp']
        balances = self.omit(response, ommited)
        currencyIds = list(balances.keys())
        for i in range(0, len(currencyIds)):
            currencyId = currencyIds[i]
            balance = self.safe_value(balances, currencyId, {})
            account = self.account()
            account['free'] = self.safe_float(balance, 'available')
            # https://github.com/ccxt/ccxt/issues/5484
            account['used'] = self.safe_float(balance, 'orders', 0.0)
            code = self.safe_currency_code(currencyId)
            result[code] = account
        return self.parse_balance(result)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        request = {
            'pair': self.market_id(symbol),
        }
        if limit is not None:
            request['depth'] = limit
        response = self.publicGetOrderBookPair(self.extend(request, params))
        timestamp = self.safe_timestamp(response, 'timestamp')
        return self.parse_order_book(response, timestamp)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         1591403940,
        #         0.024972,
        #         0.024972,
        #         0.024969,
        #         0.024969,
        #         0.49999900
        #     ]
        #
        return [
            self.safe_timestamp(ohlcv, 0),
            self.safe_float(ohlcv, 1),
            self.safe_float(ohlcv, 2),
            self.safe_float(ohlcv, 3),
            self.safe_float(ohlcv, 4),
            self.safe_float(ohlcv, 5),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        if since is None:
            since = self.milliseconds() - 86400000  # yesterday
        else:
            if self.options['fetchOHLCVWarning']:
                raise ExchangeError(self.id + " fetchOHLCV warning: CEX can return historical candles for a certain date only, self might produce an empty or None reply. Set exchange.options['fetchOHLCVWarning'] = False or add({'options': {'fetchOHLCVWarning': False}}) to constructor params to suppress self warning message.")
        ymd = self.ymd(since)
        ymd = ymd.split('-')
        ymd = ''.join(ymd)
        request = {
            'pair': market['id'],
            'yyyymmdd': ymd,
        }
        try:
            response = self.publicGetOhlcvHdYyyymmddPair(self.extend(request, params))
            #
            #     {
            #         "time":20200606,
            #         "data1m":"[[1591403940,0.024972,0.024972,0.024969,0.024969,0.49999900]]",
            #     }
            #
            key = 'data' + self.timeframes[timeframe]
            data = self.safe_string(response, key)
            ohlcvs = json.loads(data)
            return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
        except Exception as e:
            if isinstance(e, NullResponse):
                return []

    def parse_ticker(self, ticker, market=None):
        timestamp = self.safe_timestamp(ticker, 'timestamp')
        volume = self.safe_float(ticker, 'volume')
        high = self.safe_float(ticker, 'high')
        low = self.safe_float(ticker, 'low')
        bid = self.safe_float(ticker, 'bid')
        ask = self.safe_float(ticker, 'ask')
        last = self.safe_float(ticker, 'last')
        symbol = None
        if market:
            symbol = market['symbol']
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': high,
            'low': low,
            'bid': bid,
            'bidVolume': None,
            'ask': ask,
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': volume,
            'quoteVolume': None,
            'info': ticker,
        }

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        currencies = list(self.currencies.keys())
        request = {
            'currencies': '/'.join(currencies),
        }
        response = self.publicGetTickersCurrencies(self.extend(request, params))
        tickers = response['data']
        result = {}
        for t in range(0, len(tickers)):
            ticker = tickers[t]
            symbol = ticker['pair'].replace(':', '/')
            market = self.markets[symbol]
            result[symbol] = self.parse_ticker(ticker, market)
        return self.filter_by_array(result, 'symbol', symbols)

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'pair': market['id'],
        }
        ticker = self.publicGetTickerPair(self.extend(request, params))
        return self.parse_ticker(ticker, market)

    def parse_trade(self, trade, market=None):
        timestamp = self.safe_timestamp(trade, 'date')
        id = self.safe_string(trade, 'tid')
        type = None
        side = self.safe_string(trade, 'type')
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'amount')
        cost = None
        if amount is not None:
            if price is not None:
                cost = amount * price
        symbol = None
        if market is not None:
            symbol = market['symbol']
        return {
            'info': trade,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': type,
            'side': side,
            'order': None,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': None,
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'pair': market['id'],
        }
        response = self.publicGetTradeHistoryPair(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        # for market buy it requires the amount of quote currency to spend
        if (type == 'market') and (side == 'buy'):
            if self.options['createMarketBuyOrderRequiresPrice']:
                if price is None:
                    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:
                    amount = amount * price
        self.load_markets()
        request = {
            'pair': self.market_id(symbol),
            'type': side,
            'amount': amount,
        }
        if type == 'limit':
            request['price'] = price
        else:
            request['order_type'] = type
        response = self.privatePostPlaceOrderPair(self.extend(request, params))
        #
        #     {
        #         "id": "12978363524",
        #         "time": 1586610022259,
        #         "type": "buy",
        #         "price": "0.033934",
        #         "amount": "0.10722802",
        #         "pending": "0.10722802",
        #         "complete": False
        #     }
        #
        placedAmount = self.safe_float(response, 'amount')
        remaining = self.safe_float(response, 'pending')
        timestamp = self.safe_value(response, 'time')
        complete = self.safe_value(response, 'complete')
        status = 'closed' if complete else 'open'
        filled = None
        if (placedAmount is not None) and (remaining is not None):
            filled = max(placedAmount - remaining, 0)
        return {
            'id': self.safe_string(response, 'id'),
            'info': response,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'type': type,
            'side': self.safe_string(response, 'type'),
            'symbol': symbol,
            'status': status,
            'price': self.safe_float(response, 'price'),
            'amount': placedAmount,
            'cost': None,
            'average': None,
            'remaining': remaining,
            'filled': filled,
            'fee': None,
            'trades': None,
        }

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

    def parse_order(self, order, market=None):
        # Depending on the call, 'time' can be a unix int, unix string or ISO string
        # Yes, really
        timestamp = self.safe_value(order, 'time')
        if isinstance(timestamp, basestring) and timestamp.find('T') >= 0:
            # ISO8601 string
            timestamp = self.parse8601(timestamp)
        else:
            # either integer or string integer
            timestamp = int(timestamp)
        symbol = None
        if market is None:
            baseId = self.safe_string(order, 'symbol1')
            quoteId = self.safe_string(order, 'symbol2')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            if symbol in self.markets:
                market = self.market(symbol)
        status = self.parse_order_status(self.safe_string(order, 'status'))
        price = self.safe_float(order, 'price')
        amount = self.safe_float(order, 'amount')
        # sell orders can have a negative amount
        # https://github.com/ccxt/ccxt/issues/5338
        if amount is not None:
            amount = abs(amount)
        remaining = self.safe_float_2(order, 'pending', 'remains')
        filled = amount - remaining
        fee = None
        cost = None
        if market is not None:
            symbol = market['symbol']
            taCost = self.safe_float(order, 'ta:' + market['quote'])
            ttaCost = self.safe_float(order, 'tta:' + market['quote'])
            cost = self.sum(taCost, ttaCost)
            baseFee = 'fa:' + market['base']
            baseTakerFee = 'tfa:' + market['base']
            quoteFee = 'fa:' + market['quote']
            quoteTakerFee = 'tfa:' + market['quote']
            feeRate = self.safe_float(order, 'tradingFeeMaker')
            if not feeRate:
                feeRate = self.safe_float(order, 'tradingFeeTaker', feeRate)
            if feeRate:
                feeRate /= 100.0  # convert to mathematically-correct percentage coefficients: 1.0 = 100%
            if (baseFee in order) or (baseTakerFee in order):
                baseFeeCost = self.safe_float_2(order, baseFee, baseTakerFee)
                fee = {
                    'currency': market['base'],
                    'rate': feeRate,
                    'cost': baseFeeCost,
                }
            elif (quoteFee in order) or (quoteTakerFee in order):
                quoteFeeCost = self.safe_float_2(order, quoteFee, quoteTakerFee)
                fee = {
                    'currency': market['quote'],
                    'rate': feeRate,
                    'cost': quoteFeeCost,
                }
        if not cost:
            cost = price * filled
        side = order['type']
        trades = None
        orderId = order['id']
        if 'vtx' in order:
            trades = []
            for i in range(0, len(order['vtx'])):
                item = order['vtx'][i]
                tradeSide = self.safe_string(item, 'type')
                if tradeSide == 'cancel':
                    # looks like self might represent the cancelled part of an order
                    #   {id: '4426729543',
                    #     type: 'cancel',
                    #     time: '2017-09-22T00:24:30.476Z',
                    #     user: 'up106404164',
                    #     c: 'user:up106404164:a:BCH',
                    #     d: 'order:4426728375:a:BCH',
                    #     a: '0.09935956',
                    #     amount: '0.09935956',
                    #     balance: '0.42580261',
                    #     symbol: 'BCH',
                    #     order: '4426728375',
                    #     buy: null,
                    #     sell: null,
                    #     pair: null,
                    #     pos: null,
                    #     cs: '0.42580261',
                    #     ds: 0}
                    continue
                tradePrice = self.safe_float(item, 'price')
                if tradePrice is None:
                    # self represents the order
                    #   {
                    #     "a": "0.47000000",
                    #     "c": "user:up106404164:a:EUR",
                    #     "d": "order:6065499239:a:EUR",
                    #     "cs": "1432.93",
                    #     "ds": "476.72",
                    #     "id": "6065499249",
                    #     "buy": null,
                    #     "pos": null,
                    #     "pair": null,
                    #     "sell": null,
                    #     "time": "2018-04-22T13:07:22.152Z",
                    #     "type": "buy",
                    #     "user": "up106404164",
                    #     "order": "6065499239",
                    #     "amount": "-715.97000000",
                    #     "symbol": "EUR",
                    #     "balance": "1432.93000000"}
                    continue
                # todo: deal with these
                if tradeSide == 'costsNothing':
                    continue
                # --
                # if side != tradeSide:
                #     raise Error(json.dumps(order, null, 2))
                # if orderId != item['order']:
                #     raise Error(json.dumps(order, null, 2))
                # --
                # partial buy trade
                #   {
                #     "a": "0.01589885",
                #     "c": "user:up106404164:a:BTC",
                #     "d": "order:6065499239:a:BTC",
                #     "cs": "0.36300000",
                #     "ds": 0,
                #     "id": "6067991213",
                #     "buy": "6065499239",
                #     "pos": null,
                #     "pair": null,
                #     "sell": "6067991206",
                #     "time": "2018-04-22T23:09:11.773Z",
                #     "type": "buy",
                #     "user": "up106404164",
                #     "order": "6065499239",
                #     "price": 7146.5,
                #     "amount": "0.01589885",
                #     "symbol": "BTC",
                #     "balance": "0.36300000",
                #     "symbol2": "EUR",
                #     "fee_amount": "0.19"}
                # --
                # trade with zero amount, but non-zero fee
                #   {
                #     "a": "0.00000000",
                #     "c": "user:up106404164:a:EUR",
                #     "d": "order:5840654423:a:EUR",
                #     "cs": 559744,
                #     "ds": 0,
                #     "id": "5840654429",
                #     "buy": "5807238573",
                #     "pos": null,
                #     "pair": null,
                #     "sell": "5840654423",
                #     "time": "2018-03-15T03:20:14.010Z",
                #     "type": "sell",
                #     "user": "up106404164",
                #     "order": "5840654423",
                #     "price": 730,
                #     "amount": "0.00000000",
                #     "symbol": "EUR",
                #     "balance": "5597.44000000",
                #     "symbol2": "BCH",
                #     "fee_amount": "0.01"}
                # --
                # trade which should have an amount of exactly 0.002BTC
                #   {
                #     "a": "16.70000000",
                #     "c": "user:up106404164:a:GBP",
                #     "d": "order:9927386681:a:GBP",
                #     "cs": "86.90",
                #     "ds": 0,
                #     "id": "9927401610",
                #     "buy": "9927401601",
                #     "pos": null,
                #     "pair": null,
                #     "sell": "9927386681",
                #     "time": "2019-08-21T15:25:37.777Z",
                #     "type": "sell",
                #     "user": "up106404164",
                #     "order": "9927386681",
                #     "price": 8365,
                #     "amount": "16.70000000",
                #     "office": "UK",
                #     "symbol": "GBP",
                #     "balance": "86.90000000",
                #     "symbol2": "BTC",
                #     "fee_amount": "0.03"
                #   }
                tradeTimestamp = self.parse8601(self.safe_string(item, 'time'))
                tradeAmount = self.safe_float(item, 'amount')
                feeCost = self.safe_float(item, 'fee_amount')
                absTradeAmount = -tradeAmount if (tradeAmount < 0) else tradeAmount
                tradeCost = None
                if tradeSide == 'sell':
                    tradeCost = absTradeAmount
                    absTradeAmount = self.sum(feeCost, tradeCost) / tradePrice
                else:
                    tradeCost = absTradeAmount * tradePrice
                trades.append({
                    'id': self.safe_string(item, 'id'),
                    'timestamp': tradeTimestamp,
                    'datetime': self.iso8601(tradeTimestamp),
                    'order': orderId,
                    'symbol': symbol,
                    'price': tradePrice,
                    'amount': absTradeAmount,
                    'cost': tradeCost,
                    'side': tradeSide,
                    'fee': {
                        'cost': feeCost,
                        'currency': market['quote'],
                    },
                    'info': item,
                    'type': None,
                    'takerOrMaker': None,
                })
        return {
            'id': orderId,
            'clientOrderId': None,
            'datetime': self.iso8601(timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': symbol,
            'type': 'market' if (price is None) else 'limit',
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'trades': trades,
            'fee': fee,
            'info': order,
            'average': None,
        }

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {}
        method = 'privatePostOpenOrders'
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['pair'] = market['id']
            method += 'Pair'
        orders = getattr(self, method)(self.extend(request, params))
        for i in range(0, len(orders)):
            orders[i] = self.extend(orders[i], {'status': 'open'})
        return self.parse_orders(orders, market, since, limit)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        method = 'privatePostArchivedOrdersPair'
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchClosedOrders() requires a symbol argument')
        market = self.market(symbol)
        request = {'pair': market['id']}
        response = getattr(self, method)(self.extend(request, params))
        return self.parse_orders(response, market, since, limit)

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        request = {
            'id': str(id),
        }
        response = self.privatePostGetOrderTx(self.extend(request, params))
        data = self.safe_value(response, 'data', {})
        #
        #     {
        #         "id": "5442731603",
        #         "type": "sell",
        #         "time": 1516132358071,
        #         "lastTxTime": 1516132378452,
        #         "lastTx": "5442734452",
        #         "pos": null,
        #         "user": "up106404164",
        #         "status": "d",
        #         "symbol1": "ETH",
        #         "symbol2": "EUR",
        #         "amount": "0.50000000",
        #         "kind": "api",
        #         "price": "923.3386",
        #         "tfacf": "1",
        #         "fa:EUR": "0.55",
        #         "ta:EUR": "369.77",
        #         "remains": "0.00000000",
        #         "tfa:EUR": "0.22",
        #         "tta:EUR": "91.95",
        #         "a:ETH:cds": "0.50000000",
        #         "a:EUR:cds": "461.72",
        #         "f:EUR:cds": "0.77",
        #         "tradingFeeMaker": "0.15",
        #         "tradingFeeTaker": "0.23",
        #         "tradingFeeStrategy": "userVolumeAmount",
        #         "tradingFeeUserVolumeAmount": "2896912572",
        #         "orderId": "5442731603",
        #         "next": False,
        #         "vtx": [
        #             {
        #                 "id": "5442734452",
        #                 "type": "sell",
        #                 "time": "2018-01-16T19:52:58.452Z",
        #                 "user": "up106404164",
        #                 "c": "user:up106404164:a:EUR",
        #                 "d": "order:5442731603:a:EUR",
        #                 "a": "104.53000000",
        #                 "amount": "104.53000000",
        #                 "balance": "932.71000000",
        #                 "symbol": "EUR",
        #                 "order": "5442731603",
        #                 "buy": "5442734443",
        #                 "sell": "5442731603",
        #                 "pair": null,
        #                 "pos": null,
        #                 "office": null,
        #                 "cs": "932.71",
        #                 "ds": 0,
        #                 "price": 923.3386,
        #                 "symbol2": "ETH",
        #                 "fee_amount": "0.16"
        #             },
        #             {
        #                 "id": "5442731609",
        #                 "type": "sell",
        #                 "time": "2018-01-16T19:52:38.071Z",
        #                 "user": "up106404164",
        #                 "c": "user:up106404164:a:EUR",
        #                 "d": "order:5442731603:a:EUR",
        #                 "a": "91.73000000",
        #                 "amount": "91.73000000",
        #                 "balance": "563.49000000",
        #                 "symbol": "EUR",
        #                 "order": "5442731603",
        #                 "buy": "5442618127",
        #                 "sell": "5442731603",
        #                 "pair": null,
        #                 "pos": null,
        #                 "office": null,
        #                 "cs": "563.49",
        #                 "ds": 0,
        #                 "price": 924.0092,
        #                 "symbol2": "ETH",
        #                 "fee_amount": "0.22"
        #             },
        #             {
        #                 "id": "5442731604",
        #                 "type": "sell",
        #                 "time": "2018-01-16T19:52:38.071Z",
        #                 "user": "up106404164",
        #                 "c": "order:5442731603:a:ETH",
        #                 "d": "user:up106404164:a:ETH",
        #                 "a": "0.50000000",
        #                 "amount": "-0.50000000",
        #                 "balance": "15.80995000",
        #                 "symbol": "ETH",
        #                 "order": "5442731603",
        #                 "buy": null,
        #                 "sell": null,
        #                 "pair": null,
        #                 "pos": null,
        #                 "office": null,
        #                 "cs": "0.50000000",
        #                 "ds": "15.80995000"
        #             }
        #         ]
        #     }
        #
        return self.parse_order(data)

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'limit': limit,
            'pair': market['id'],
            'dateFrom': since,
        }
        response = self.privatePostArchivedOrdersPair(self.extend(request, params))
        results = []
        for i in range(0, len(response)):
            # cancelled(unfilled):
            #    {id: '4005785516',
            #     type: 'sell',
            #     time: '2017-07-18T19:08:34.223Z',
            #     lastTxTime: '2017-07-18T19:08:34.396Z',
            #     lastTx: '4005785522',
            #     pos: null,
            #     status: 'c',
            #     symbol1: 'ETH',
            #     symbol2: 'GBP',
            #     amount: '0.20000000',
            #     price: '200.5625',
            #     remains: '0.20000000',
            #     'a:ETH:cds': '0.20000000',
            #     tradingFeeMaker: '0',
            #     tradingFeeTaker: '0.16',
            #     tradingFeeUserVolumeAmount: '10155061217',
            #     orderId: '4005785516'}
            # --
            # cancelled(partially filled buy):
            #    {id: '4084911657',
            #     type: 'buy',
            #     time: '2017-08-05T03:18:39.596Z',
            #     lastTxTime: '2019-03-19T17:37:46.404Z',
            #     lastTx: '8459265833',
            #     pos: null,
            #     status: 'cd',
            #     symbol1: 'BTC',
            #     symbol2: 'GBP',
            #     amount: '0.05000000',
            #     price: '2241.4692',
            #     tfacf: '1',
            #     remains: '0.03910535',
            #     'tfa:GBP': '0.04',
            #     'tta:GBP': '24.39',
            #     'a:BTC:cds': '0.01089465',
            #     'a:GBP:cds': '112.26',
            #     'f:GBP:cds': '0.04',
            #     tradingFeeMaker: '0',
            #     tradingFeeTaker: '0.16',
            #     tradingFeeUserVolumeAmount: '13336396963',
            #     orderId: '4084911657'}
            # --
            # cancelled(partially filled sell):
            #    {id: '4426728375',
            #     type: 'sell',
            #     time: '2017-09-22T00:24:20.126Z',
            #     lastTxTime: '2017-09-22T00:24:30.476Z',
            #     lastTx: '4426729543',
            #     pos: null,
            #     status: 'cd',
            #     symbol1: 'BCH',
            #     symbol2: 'BTC',
            #     amount: '0.10000000',
            #     price: '0.11757182',
            #     tfacf: '1',
            #     remains: '0.09935956',
            #     'tfa:BTC': '0.00000014',
            #     'tta:BTC': '0.00007537',
            #     'a:BCH:cds': '0.10000000',
            #     'a:BTC:cds': '0.00007537',
            #     'f:BTC:cds': '0.00000014',
            #     tradingFeeMaker: '0',
            #     tradingFeeTaker: '0.18',
            #     tradingFeeUserVolumeAmount: '3466715450',
            #     orderId: '4426728375'}
            # --
            # filled:
            #    {id: '5342275378',
            #     type: 'sell',
            #     time: '2018-01-04T00:28:12.992Z',
            #     lastTxTime: '2018-01-04T00:28:12.992Z',
            #     lastTx: '5342275393',
            #     pos: null,
            #     status: 'd',
            #     symbol1: 'BCH',
            #     symbol2: 'BTC',
            #     amount: '0.10000000',
            #     kind: 'api',
            #     price: '0.17',
            #     remains: '0.00000000',
            #     'tfa:BTC': '0.00003902',
            #     'tta:BTC': '0.01699999',
            #     'a:BCH:cds': '0.10000000',
            #     'a:BTC:cds': '0.01699999',
            #     'f:BTC:cds': '0.00003902',
            #     tradingFeeMaker: '0.15',
            #     tradingFeeTaker: '0.23',
            #     tradingFeeUserVolumeAmount: '1525951128',
            #     orderId: '5342275378'}
            # --
            # market order(buy):
            #    {"id": "6281946200",
            #     "pos": null,
            #     "time": "2018-05-23T11:55:43.467Z",
            #     "type": "buy",
            #     "amount": "0.00000000",
            #     "lastTx": "6281946210",
            #     "status": "d",
            #     "amount2": "20.00",
            #     "orderId": "6281946200",
            #     "remains": "0.00000000",
            #     "symbol1": "ETH",
            #     "symbol2": "EUR",
            #     "tfa:EUR": "0.05",
            #     "tta:EUR": "19.94",
            #     "a:ETH:cds": "0.03764100",
            #     "a:EUR:cds": "20.00",
            #     "f:EUR:cds": "0.05",
            #     "lastTxTime": "2018-05-23T11:55:43.467Z",
            #     "tradingFeeTaker": "0.25",
            #     "tradingFeeUserVolumeAmount": "55998097"}
            # --
            # market order(sell):
            #   {"id": "6282200948",
            #     "pos": null,
            #     "time": "2018-05-23T12:42:58.315Z",
            #     "type": "sell",
            #     "amount": "-0.05000000",
            #     "lastTx": "6282200958",
            #     "status": "d",
            #     "orderId": "6282200948",
            #     "remains": "0.00000000",
            #     "symbol1": "ETH",
            #     "symbol2": "EUR",
            #     "tfa:EUR": "0.07",
            #     "tta:EUR": "26.49",
            #     "a:ETH:cds": "0.05000000",
            #     "a:EUR:cds": "26.49",
            #     "f:EUR:cds": "0.07",
            #     "lastTxTime": "2018-05-23T12:42:58.315Z",
            #     "tradingFeeTaker": "0.25",
            #     "tradingFeeUserVolumeAmount": "56294576"}
            order = response[i]
            status = self.parse_order_status(self.safe_string(order, 'status'))
            baseId = self.safe_string(order, 'symbol1')
            quoteId = self.safe_string(order, 'symbol2')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            side = self.safe_string(order, 'type')
            baseAmount = self.safe_float(order, 'a:' + baseId + ':cds')
            quoteAmount = self.safe_float(order, 'a:' + quoteId + ':cds')
            fee = self.safe_float(order, 'f:' + quoteId + ':cds')
            amount = self.safe_float(order, 'amount')
            price = self.safe_float(order, 'price')
            remaining = self.safe_float(order, 'remains')
            filled = amount - remaining
            orderAmount = None
            cost = None
            average = None
            type = None
            if not price:
                type = 'market'
                orderAmount = baseAmount
                cost = quoteAmount
                average = orderAmount / cost
            else:
                ta = self.safe_float(order, 'ta:' + quoteId, 0)
                tta = self.safe_float(order, 'tta:' + quoteId, 0)
                fa = self.safe_float(order, 'fa:' + quoteId, 0)
                tfa = self.safe_float(order, 'tfa:' + quoteId, 0)
                if side == 'sell':
                    cost = self.sum(self.sum(ta, tta), self.sum(fa, tfa))
                else:
                    cost = self.sum(ta, tta) - self.sum(fa, tfa)
                type = 'limit'
                orderAmount = amount
                average = cost / filled
            time = self.safe_string(order, 'time')
            lastTxTime = self.safe_string(order, 'lastTxTime')
            timestamp = self.parse8601(time)
            results.append({
                'id': self.safe_string(order, 'id'),
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
                'lastUpdated': self.parse8601(lastTxTime),
                'status': status,
                'symbol': symbol,
                'side': side,
                'price': price,
                'amount': orderAmount,
                'average': average,
                'type': type,
                'filled': filled,
                'cost': cost,
                'remaining': remaining,
                'fee': {
                    'cost': fee,
                    'currency': quote,
                },
                'info': order,
            })
        return results

    def parse_order_status(self, status):
        return self.safe_string(self.options['order']['status'], status, status)

    def edit_order(self, id, symbol, type, side, amount=None, price=None, params={}):
        if amount is None:
            raise ArgumentsRequired(self.id + ' editOrder() requires a amount argument')
        if price is None:
            raise ArgumentsRequired(self.id + ' editOrder() requires a price argument')
        self.load_markets()
        market = self.market(symbol)
        # see: https://cex.io/rest-api#/definitions/CancelReplaceOrderRequest
        request = {
            'pair': market['id'],
            'type': side,
            'amount': amount,
            'price': price,
            'order_id': id,
        }
        response = self.privatePostCancelReplaceOrderPair(self.extend(request, params))
        return self.parse_order(response, market)

    def fetch_deposit_address(self, code, params={}):
        if code == 'XRP' or code == 'XLM':
            # https://github.com/ccxt/ccxt/pull/2327#issuecomment-375204856
            raise NotSupported(self.id + ' fetchDepositAddress does not support XRP and XLM addresses yet(awaiting docs from CEX.io)')
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = self.privatePostGetAddress(self.extend(request, params))
        address = self.safe_string(response, 'data')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': None,
            'info': response,
        }

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

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'] + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if api == 'public':
            if query:
                url += '?' + self.urlencode(query)
        else:
            self.check_required_credentials()
            nonce = str(self.nonce())
            auth = nonce + self.uid + self.apiKey
            signature = self.hmac(self.encode(auth), self.encode(self.secret))
            body = self.json(self.extend({
                'key': self.apiKey,
                'signature': signature.upper(),
                'nonce': nonce,
            }, query))
            headers = {
                'Content-Type': 'application/json',
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if isinstance(response, list):
            return response  # public endpoints may return []-arrays
        if body == 'true':
            return
        if response is None:
            raise NullResponse(self.id + ' returned ' + self.json(response))
        if 'e' in response:
            if 'ok' in response:
                if response['ok'] == 'ok':
                    return
        if 'error' in response:
            message = self.safe_string(response, 'error')
            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)
