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

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

from ccxt.async_support.base.exchange import Exchange
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import BadResponse
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidAddress
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class probit(Exchange):

    def describe(self):
        return self.deep_extend(super(probit, self).describe(), {
            'id': 'probit',
            'name': 'ProBit',
            'countries': ['SC', 'KR'],  # Seychelles, South Korea
            'rateLimit': 250,  # ms
            'has': {
                'CORS': True,
                'fetchTime': True,
                'fetchMarkets': True,
                'fetchCurrencies': True,
                'fetchTickers': True,
                'fetchTicker': True,
                'fetchOHLCV': True,
                'fetchOrderBook': True,
                'fetchTrades': True,
                'fetchBalance': True,
                'createOrder': True,
                'createMarketOrder': True,
                'cancelOrder': True,
                'fetchOrder': True,
                'fetchOpenOrders': True,
                'fetchClosedOrders': True,
                'fetchMyTrades': True,
                'fetchDepositAddress': True,
                'withdraw': True,
                'signIn': True,
            },
            'timeframes': {
                '1m': '1m',
                '3m': '3m',
                '5m': '5m',
                '10m': '10m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '4h': '4h',
                '6h': '6h',
                '12h': '12h',
                '1d': '1D',
                '1w': '1W',
                '1M': '1M',
            },
            'version': 'v1',
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/79268032-c4379480-7ea2-11ea-80b3-dd96bb29fd0d.jpg',
                'api': {
                    'accounts': 'https://accounts.probit.com',
                    'public': 'https://api.probit.com/api/exchange',
                    'private': 'https://api.probit.com/api/exchange',
                },
                'www': 'https://www.probit.com',
                'doc': [
                    'https://docs-en.probit.com',
                    'https://docs-ko.probit.com',
                ],
                'fees': 'https://support.probit.com/hc/en-us/articles/360020968611-Trading-Fees',
                'referral': 'https://www.probit.com/r/34608773',
            },
            'api': {
                'public': {
                    'get': [
                        'market',
                        'currency',
                        'currency_with_platform',
                        'time',
                        'ticker',
                        'order_book',
                        'trade',
                        'candle',
                    ],
                },
                'private': {
                    'post': [
                        'new_order',
                        'cancel_order',
                        'withdrawal',
                    ],
                    'get': [
                        'balance',
                        'order',
                        'open_order',
                        'order_history',
                        'trade_history',
                        'deposit_address',
                    ],
                },
                'accounts': {
                    'post': [
                        'token',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'maker': 0.2 / 100,
                    'taker': 0.2 / 100,
                },
            },
            'exceptions': {
                'exact': {
                    'UNAUTHORIZED': AuthenticationError,
                    'INVALID_ARGUMENT': BadRequest,  # Parameters are not a valid format, parameters are empty, or out of range, or a parameter was sent when not required.
                    'TRADING_UNAVAILABLE': ExchangeNotAvailable,
                    'NOT_ENOUGH_BALANCE': InsufficientFunds,
                    'NOT_ALLOWED_COMBINATION': BadRequest,
                    'INVALID_ORDER': InvalidOrder,  # Requested order does not exist, or it is not your order
                    'RATE_LIMIT_EXCEEDED': RateLimitExceeded,  # You are sending requests too frequently. Please try it later.
                    'MARKET_UNAVAILABLE': ExchangeNotAvailable,  # Market is closed today
                    'INVALID_MARKET': BadSymbol,  # Requested market is not exist
                    'MARKET_CLOSED': BadSymbol,  # {"errorCode":"MARKET_CLOSED"}
                    'MARKET_NOT_FOUND': BadSymbol,  # {"errorCode":"MARKET_NOT_FOUND","message":"8e2b8496-0a1e-5beb-b990-a205b902eabe","details":{}}
                    'INVALID_CURRENCY': BadRequest,  # Requested currency is not exist on ProBit system
                    'TOO_MANY_OPEN_ORDERS': DDoSProtection,  # Too many open orders
                    'DUPLICATE_ADDRESS': InvalidAddress,  # Address already exists in withdrawal address list
                },
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
            },
            'precisionMode': TICK_SIZE,
            'options': {
                'createMarketBuyOrderRequiresPrice': True,
                'timeInForce': {
                    'limit': 'gtc',
                    'market': 'ioc',
                },
            },
            'commonCurrencies': {
                'AUTO': 'Cube',
                'BTCBEAR': 'BEAR',
                'BTCBULL': 'BULL',
                'CBC': 'CryptoBharatCoin',
                'EPS': 'Epanus',  # conflict with EPS Ellipsis https://github.com/ccxt/ccxt/issues/8909
                'HBC': 'Hybrid Bank Cash',
                'UNI': 'UNICORN Token',
            },
        })

    async def fetch_markets(self, params={}):
        response = await self.publicGetMarket(params)
        #
        #     {
        #         "data":[
        #             {
        #                 "id":"MONA-USDT",
        #                 "base_currency_id":"MONA",
        #                 "quote_currency_id":"USDT",
        #                 "min_price":"0.001",
        #                 "max_price":"9999999999999999",
        #                 "price_increment":"0.001",
        #                 "min_quantity":"0.0001",
        #                 "max_quantity":"9999999999999999",
        #                 "quantity_precision":4,
        #                 "min_cost":"1",
        #                 "max_cost":"9999999999999999",
        #                 "cost_precision":8,
        #                 "taker_fee_rate":"0.2",
        #                 "maker_fee_rate":"0.2",
        #                 "show_in_ui":true,
        #                 "closed":false
        #             },
        #         ]
        #     }
        #
        markets = self.safe_value(response, 'data', [])
        result = []
        for i in range(0, len(markets)):
            market = markets[i]
            id = self.safe_string(market, 'id')
            baseId = self.safe_string(market, 'base_currency_id')
            quoteId = self.safe_string(market, 'quote_currency_id')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            closed = self.safe_value(market, 'closed', False)
            active = not closed
            amountPrecision = self.safe_string(market, 'quantity_precision')
            costPrecision = self.safe_string(market, 'cost_precision')
            amountTickSize = self.parse_precision(amountPrecision)
            costTickSize = self.parse_precision(costPrecision)
            precision = {
                'amount': self.parse_number(amountTickSize),
                'price': self.safe_number(market, 'price_increment'),
                'cost': self.parse_number(costTickSize),
            }
            takerFeeRate = self.safe_string(market, 'taker_fee_rate')
            taker = Precise.string_div(takerFeeRate, '100')
            makerFeeRate = self.safe_string(market, 'maker_fee_rate')
            maker = Precise.string_div(makerFeeRate, '100')
            result.append({
                'id': id,
                'info': market,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': active,
                'precision': precision,
                'taker': self.parse_number(taker),
                'maker': self.parse_number(maker),
                'limits': {
                    'amount': {
                        'min': self.safe_number(market, 'min_quantity'),
                        'max': self.safe_number(market, 'max_quantity'),
                    },
                    'price': {
                        'min': self.safe_number(market, 'min_price'),
                        'max': self.safe_number(market, 'max_price'),
                    },
                    'cost': {
                        'min': self.safe_number(market, 'min_cost'),
                        'max': self.safe_number(market, 'max_cost'),
                    },
                },
            })
        return result

    async def fetch_currencies(self, params={}):
        response = await self.publicGetCurrencyWithPlatform(params)
        #
        #     {
        #         "data":[
        #             {
        #                 "id":"USDT",
        #                 "display_name":{"ko-kr":"테더","en-us":"Tether"},
        #                 "show_in_ui":true,
        #                 "platform":[
        #                     {
        #                         "id":"ETH",
        #                         "priority":1,
        #                         "deposit":true,
        #                         "withdrawal":true,
        #                         "currency_id":"USDT",
        #                         "precision":6,
        #                         "min_confirmation_count":15,
        #                         "require_destination_tag":false,
        #                         "display_name":{"name":{"ko-kr":"ERC-20","en-us":"ERC-20"}},
        #                         "min_deposit_amount":"0",
        #                         "min_withdrawal_amount":"1",
        #                         "withdrawal_fee":[
        #                             {"amount":"0.01","priority":2,"currency_id":"ETH"},
        #                             {"amount":"1.5","priority":1,"currency_id":"USDT"},
        #                         ],
        #                         "deposit_fee":{},
        #                         "suspended_reason":"",
        #                         "deposit_suspended":false,
        #                         "withdrawal_suspended":false
        #                     },
        #                     {
        #                         "id":"OMNI",
        #                         "priority":2,
        #                         "deposit":true,
        #                         "withdrawal":true,
        #                         "currency_id":"USDT",
        #                         "precision":6,
        #                         "min_confirmation_count":3,
        #                         "require_destination_tag":false,
        #                         "display_name":{"name":{"ko-kr":"OMNI","en-us":"OMNI"}},
        #                         "min_deposit_amount":"0",
        #                         "min_withdrawal_amount":"5",
        #                         "withdrawal_fee":[{"amount":"5","priority":1,"currency_id":"USDT"}],
        #                         "deposit_fee":{},
        #                         "suspended_reason":"wallet_maintenance",
        #                         "deposit_suspended":false,
        #                         "withdrawal_suspended":false
        #                     }
        #                 ],
        #                 "stakeable":false,
        #                 "unstakeable":false,
        #                 "auto_stake":false,
        #                 "auto_stake_amount":"0"
        #             }
        #         ]
        #     }
        #
        currencies = self.safe_value(response, 'data')
        result = {}
        for i in range(0, len(currencies)):
            currency = currencies[i]
            id = self.safe_string(currency, 'id')
            code = self.safe_currency_code(id)
            displayName = self.safe_value(currency, 'display_name')
            name = self.safe_string(displayName, 'en-us')
            platforms = self.safe_value(currency, 'platform', [])
            platformsByPriority = self.sort_by(platforms, 'priority')
            platform = self.safe_value(platformsByPriority, 0, {})
            precision = self.safe_integer(platform, 'precision')
            depositSuspended = self.safe_value(platform, 'deposit_suspended')
            withdrawalSuspended = self.safe_value(platform, 'withdrawal_suspended')
            active = not (depositSuspended and withdrawalSuspended)
            withdrawalFees = self.safe_value(platform, 'withdrawal_fee', {})
            fees = []
            # sometimes the withdrawal fee is an empty object
            # [{'amount': '0.015', 'priority': 1, 'currency_id': 'ETH'}, {}]
            for j in range(0, len(withdrawalFees)):
                withdrawalFee = withdrawalFees[j]
                amount = self.safe_number(withdrawalFee, 'amount')
                priority = self.safe_integer(withdrawalFee, 'priority')
                if (amount is not None) and (priority is not None):
                    fees.append(withdrawalFee)
            withdrawalFeesByPriority = self.sort_by(fees, 'priority')
            withdrawalFee = self.safe_value(withdrawalFeesByPriority, 0, {})
            fee = self.safe_number(withdrawalFee, 'amount')
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': name,
                'active': active,
                'fee': fee,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': math.pow(10, -precision),
                        'max': math.pow(10, precision),
                    },
                    'deposit': {
                        'min': self.safe_number(platform, 'min_deposit_amount'),
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_number(platform, 'min_withdrawal_amount'),
                        'max': None,
                    },
                },
            }
        return result

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await self.privateGetBalance(params)
        #
        #     {
        #         data: [
        #             {
        #                 "currency_id":"XRP",
        #                 "total":"100",
        #                 "available":"0",
        #             }
        #         ]
        #     }
        #
        result = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        data = self.safe_value(response, 'data')
        for i in range(0, len(data)):
            balance = data[i]
            currencyId = self.safe_string(balance, 'currency_id')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['total'] = self.safe_string(balance, 'total')
            account['free'] = self.safe_string(balance, 'available')
            result[code] = account
        return self.parse_balance(result, False)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_id': market['id'],
        }
        response = await self.publicGetOrderBook(self.extend(request, params))
        #
        #     {
        #         data: [
        #             {side: 'buy', price: '0.000031', quantity: '10'},
        #             {side: 'buy', price: '0.00356007', quantity: '4.92156877'},
        #             {side: 'sell', price: '0.1857', quantity: '0.17'},
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        dataBySide = self.group_by(data, 'side')
        return self.parse_order_book(dataBySide, symbol, None, 'buy', 'sell', 'price', 'quantity')

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        request = {}
        if symbols is not None:
            marketIds = self.market_ids(symbols)
            request['market_ids'] = ','.join(marketIds)
        response = await self.publicGetTicker(self.extend(request, params))
        #
        #     {
        #         "data":[
        #             {
        #                 "last":"0.022902",
        #                 "low":"0.021693",
        #                 "high":"0.024093",
        #                 "change":"-0.000047",
        #                 "base_volume":"15681.986",
        #                 "quote_volume":"360.514403624",
        #                 "market_id":"ETH-BTC",
        #                 "time":"2020-04-12T18:43:38.000Z"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_tickers(data, symbols)

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_ids': market['id'],
        }
        response = await self.publicGetTicker(self.extend(request, params))
        #
        #     {
        #         "data":[
        #             {
        #                 "last":"0.022902",
        #                 "low":"0.021693",
        #                 "high":"0.024093",
        #                 "change":"-0.000047",
        #                 "base_volume":"15681.986",
        #                 "quote_volume":"360.514403624",
        #                 "market_id":"ETH-BTC",
        #                 "time":"2020-04-12T18:43:38.000Z"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        ticker = self.safe_value(data, 0)
        if ticker is None:
            raise BadResponse(self.id + ' fetchTicker() returned an empty response')
        return self.parse_ticker(ticker, market)

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         "last":"0.022902",
        #         "low":"0.021693",
        #         "high":"0.024093",
        #         "change":"-0.000047",
        #         "base_volume":"15681.986",
        #         "quote_volume":"360.514403624",
        #         "market_id":"ETH-BTC",
        #         "time":"2020-04-12T18:43:38.000Z"
        #     }
        #
        timestamp = self.parse8601(self.safe_string(ticker, 'time'))
        marketId = self.safe_string(ticker, 'market_id')
        symbol = self.safe_symbol(marketId, market, '-')
        close = self.safe_number(ticker, 'last')
        change = self.safe_number(ticker, 'change')
        percentage = None
        open = None
        if change is not None:
            if close is not None:
                open = close - change
                percentage = (change / open) * 100
        baseVolume = self.safe_number(ticker, 'base_volume')
        quoteVolume = self.safe_number(ticker, 'quote_volume')
        vwap = self.vwap(baseVolume, quoteVolume)
        return {
            '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': open,
            'close': close,
            'last': close,
            'previousClose': None,  # previous day close
            'change': change,
            'percentage': percentage,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = None
        request = {
            'limit': 100,
            'start_time': self.iso8601(0),
            'end_time': self.iso8601(self.milliseconds()),
        }
        if symbol is not None:
            market = self.market(symbol)
            request['market_id'] = market['id']
        if since is not None:
            request['start_time'] = self.iso8601(since)
        if limit is not None:
            request['limit'] = limit
        response = await self.privateGetTradeHistory(self.extend(request, params))
        #
        #     {
        #         data: [
        #             {
        #                 "id":"BTC-USDT:183566",
        #                 "order_id":"17209376",
        #                 "side":"sell",
        #                 "fee_amount":"0.657396569175",
        #                 "fee_currency_id":"USDT",
        #                 "status":"settled",
        #                 "price":"6573.96569175",
        #                 "quantity":"0.1",
        #                 "cost":"657.396569175",
        #                 "time":"2018-08-10T06:06:46.000Z",
        #                 "market_id":"BTC-USDT"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_trades(data, market, since, limit)

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_id': market['id'],
            'limit': 100,
            'start_time': '1970-01-01T00:00:00.000Z',
            'end_time': self.iso8601(self.milliseconds()),
        }
        if since is not None:
            request['start_time'] = self.iso8601(since)
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetTrade(self.extend(request, params))
        #
        #     {
        #         "data":[
        #             {
        #                 "id":"ETH-BTC:3331886",
        #                 "price":"0.022981",
        #                 "quantity":"12.337",
        #                 "time":"2020-04-12T20:55:42.371Z",
        #                 "side":"sell",
        #                 "tick_direction":"down"
        #             },
        #             {
        #                 "id":"ETH-BTC:3331885",
        #                 "price":"0.022982",
        #                 "quantity":"6.472",
        #                 "time":"2020-04-12T20:55:39.652Z",
        #                 "side":"sell",
        #                 "tick_direction":"down"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_trades(data, market, since, limit)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         "id":"ETH-BTC:3331886",
        #         "price":"0.022981",
        #         "quantity":"12.337",
        #         "time":"2020-04-12T20:55:42.371Z",
        #         "side":"sell",
        #         "tick_direction":"down"
        #     }
        #
        # fetchMyTrades(private)
        #
        #     {
        #         "id":"BTC-USDT:183566",
        #         "order_id":"17209376",
        #         "side":"sell",
        #         "fee_amount":"0.657396569175",
        #         "fee_currency_id":"USDT",
        #         "status":"settled",
        #         "price":"6573.96569175",
        #         "quantity":"0.1",
        #         "cost":"657.396569175",
        #         "time":"2018-08-10T06:06:46.000Z",
        #         "market_id":"BTC-USDT"
        #     }
        #
        timestamp = self.parse8601(self.safe_string(trade, 'time'))
        id = self.safe_string(trade, 'id')
        marketId = None
        if id is not None:
            parts = id.split(':')
            marketId = self.safe_string(parts, 0)
        marketId = self.safe_string(trade, 'market_id', marketId)
        symbol = self.safe_symbol(marketId, market, '-')
        side = self.safe_string(trade, 'side')
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'quantity')
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        cost = self.parse_number(Precise.string_mul(priceString, amountString))
        orderId = self.safe_string(trade, 'order_id')
        feeCost = self.safe_number(trade, 'fee_amount')
        fee = None
        if feeCost is not None:
            feeCurrencyId = self.safe_string(trade, 'fee_currency_id')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    async def fetch_time(self, params={}):
        response = await self.publicGetTime(params)
        #
        #     {"data":"2020-04-12T18:54:25.390Z"}
        #
        timestamp = self.parse8601(self.safe_string(response, 'data'))
        return timestamp

    def normalize_ohlcv_timestamp(self, timestamp, timeframe, after=False):
        duration = self.parse_timeframe(timeframe)
        if timeframe == '1M':
            iso8601 = self.iso8601(timestamp)
            parts = iso8601.split('-')
            year = self.safe_string(parts, 0)
            month = self.safe_integer(parts, 1)
            if after:
                month = self.sum(month, 1)
            if month < 10:
                month = '0' + str(month)
            else:
                month = str(month)
            return year + '-' + month + '-01T00:00:00.000Z'
        elif timeframe == '1w':
            timestamp = int(timestamp / 1000)
            firstSunday = 259200  # 1970-01-04T00:00:00.000Z
            difference = timestamp - firstSunday
            numWeeks = self.integer_divide(difference, duration)
            previousSunday = self.sum(firstSunday, numWeeks * duration)
            if after:
                previousSunday = self.sum(previousSunday, duration)
            return self.iso8601(previousSunday * 1000)
        else:
            timestamp = int(timestamp / 1000)
            timestamp = duration * int(timestamp / duration)
            if after:
                timestamp = self.sum(timestamp, duration)
            return self.iso8601(timestamp * 1000)

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        interval = self.timeframes[timeframe]
        limit = 100 if (limit is None) else limit
        requestLimit = self.sum(limit, 1)
        requestLimit = min(1000, requestLimit)  # max 1000
        request = {
            'market_ids': market['id'],
            'interval': interval,
            'sort': 'asc',  # 'asc' will always include the start_time, 'desc' will always include end_time
            'limit': requestLimit,  # max 1000
        }
        now = self.milliseconds()
        duration = self.parse_timeframe(timeframe)
        startTime = since
        endTime = now
        if since is None:
            if limit is None:
                raise ArgumentsRequired(self.id + ' fetchOHLCV() requires either a since argument or a limit argument')
            else:
                startTime = now - limit * duration * 1000
        else:
            if limit is None:
                endTime = now
            else:
                endTime = self.sum(since, self.sum(limit, 1) * duration * 1000)
        startTimeNormalized = self.normalize_ohlcv_timestamp(startTime, timeframe)
        endTimeNormalized = self.normalize_ohlcv_timestamp(endTime, timeframe, True)
        request['start_time'] = startTimeNormalized
        request['end_time'] = endTimeNormalized
        response = await self.publicGetCandle(self.extend(request, params))
        #
        #     {
        #         "data":[
        #             {
        #                 "market_id":"ETH-BTC",
        #                 "open":"0.02811",
        #                 "close":"0.02811",
        #                 "low":"0.02811",
        #                 "high":"0.02811",
        #                 "base_volume":"0.0005",
        #                 "quote_volume":"0.000014055",
        #                 "start_time":"2018-11-30T18:19:00.000Z",
        #                 "end_time":"2018-11-30T18:20:00.000Z"
        #             },
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "market_id":"ETH-BTC",
        #         "open":"0.02811",
        #         "close":"0.02811",
        #         "low":"0.02811",
        #         "high":"0.02811",
        #         "base_volume":"0.0005",
        #         "quote_volume":"0.000014055",
        #         "start_time":"2018-11-30T18:19:00.000Z",
        #         "end_time":"2018-11-30T18:20:00.000Z"
        #     }
        #
        return [
            self.parse8601(self.safe_string(ohlcv, 'start_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, 'base_volume'),
        ]

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        since = self.parse8601(since)
        request = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['market_id'] = market['id']
        response = await self.privateGetOpenOrder(self.extend(request, params))
        data = self.safe_value(response, 'data')
        return self.parse_orders(data, market, since, limit)

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            'start_time': self.iso8601(0),
            'end_time': self.iso8601(self.milliseconds()),
            'limit': 100,
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['market_id'] = market['id']
        if since:
            request['start_time'] = self.iso8601(since)
        if limit:
            request['limit'] = limit
        response = await self.privateGetOrderHistory(self.extend(request, params))
        data = self.safe_value(response, 'data')
        return self.parse_orders(data, market, since, limit)

    async def fetch_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_id': market['id'],
        }
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client_order_id')
        if clientOrderId is not None:
            request['client_order_id'] = clientOrderId
        else:
            request['order_id'] = id
        query = self.omit(params, ['clientOrderId', 'client_order_id'])
        response = await self.privateGetOrder(self.extend(request, query))
        data = self.safe_value(response, 'data', [])
        order = self.safe_value(data, 0)
        return self.parse_order(order, market)

    def parse_order_status(self, status):
        statuses = {
            'open': 'open',
            'cancelled': 'canceled',
            'filled': 'closed',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        #     {
        #         id: string,
        #         user_id: string,
        #         market_id: string,
        #         type: 'orderType',
        #         side: 'side',
        #         quantity: string,
        #         limit_price: string,
        #         time_in_force: 'timeInForce',
        #         filled_cost: string,
        #         filled_quantity: string,
        #         open_quantity: string,
        #         cancelled_quantity: string,
        #         status: 'orderStatus',
        #         time: 'date',
        #         client_order_id: string,
        #     }
        #
        status = self.parse_order_status(self.safe_string(order, 'status'))
        id = self.safe_string(order, 'id')
        type = self.safe_string(order, 'type')
        side = self.safe_string(order, 'side')
        marketId = self.safe_string(order, 'market_id')
        symbol = self.safe_symbol(marketId, market, '-')
        timestamp = self.parse8601(self.safe_string(order, 'time'))
        price = self.safe_number(order, 'limit_price')
        filled = self.safe_number(order, 'filled_quantity')
        remaining = self.safe_number(order, 'open_quantity')
        canceledAmount = self.safe_number(order, 'cancelled_quantity')
        if canceledAmount is not None:
            remaining = self.sum(remaining, canceledAmount)
        amount = self.safe_number(order, 'quantity', self.sum(filled, remaining))
        cost = self.safe_number_2(order, 'filled_cost', 'cost')
        if type == 'market':
            price = None
        clientOrderId = self.safe_string(order, 'client_order_id')
        if clientOrderId == '':
            clientOrderId = None
        timeInForce = self.safe_string_upper(order, 'time_in_force')
        return self.safe_order({
            'id': id,
            'info': order,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'side': side,
            'status': status,
            'price': price,
            'stopPrice': None,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'average': None,
            'cost': cost,
            'fee': None,
            'trades': None,
        })

    def cost_to_precision(self, symbol, cost):
        return self.decimal_to_precision(cost, TRUNCATE, self.markets[symbol]['precision']['cost'], self.precisionMode)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        options = self.safe_value(self.options, 'timeInForce')
        defaultTimeInForce = self.safe_value(options, type)
        timeInForce = self.safe_string_2(params, 'timeInForce', 'time_in_force', defaultTimeInForce)
        request = {
            'market_id': market['id'],
            'type': type,
            'side': side,
            'time_in_force': timeInForce,
        }
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client_order_id')
        if clientOrderId is not None:
            request['client_order_id'] = clientOrderId
        costToPrecision = None
        if type == 'limit':
            request['limit_price'] = self.price_to_precision(symbol, price)
            request['quantity'] = self.amount_to_precision(symbol, amount)
        elif type == 'market':
            # for market buy it requires the amount of quote currency to spend
            if side == 'buy':
                cost = self.safe_number(params, 'cost')
                createMarketBuyOrderRequiresPrice = self.safe_value(self.options, 'createMarketBuyOrderRequiresPrice', True)
                if createMarketBuyOrderRequiresPrice:
                    if price is not None:
                        if cost is None:
                            cost = amount * price
                    elif cost is None:
                        raise InvalidOrder(self.id + " createOrder() requires the price argument for 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 and supply the total cost value in the 'amount' argument or in the 'cost' extra parameter(the exchange-specific behaviour)")
                else:
                    cost = amount if (cost is None) else cost
                costToPrecision = self.cost_to_precision(symbol, cost)
                request['cost'] = costToPrecision
            else:
                request['quantity'] = self.amount_to_precision(symbol, amount)
        query = self.omit(params, ['timeInForce', 'time_in_force', 'clientOrderId', 'client_order_id'])
        response = await self.privatePostNewOrder(self.extend(request, query))
        #
        #     {
        #         data: {
        #             id: string,
        #             user_id: string,
        #             market_id: string,
        #             type: 'orderType',
        #             side: 'side',
        #             quantity: string,
        #             limit_price: string,
        #             time_in_force: 'timeInForce',
        #             filled_cost: string,
        #             filled_quantity: string,
        #             open_quantity: string,
        #             cancelled_quantity: string,
        #             status: 'orderStatus',
        #             time: 'date',
        #             client_order_id: string,
        #         }
        #     }
        #
        data = self.safe_value(response, 'data')
        order = self.parse_order(data, market)
        # a workaround for incorrect huge amounts
        # returned by the exchange on market buys
        if (type == 'market') and (side == 'buy'):
            order['amount'] = None
            order['cost'] = float(costToPrecision)
            order['remaining'] = None
        return order

    async def cancel_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_id': market['id'],
            'order_id': id,
        }
        response = await self.privatePostCancelOrder(self.extend(request, params))
        data = self.safe_value(response, 'data')
        return self.parse_order(data)

    def parse_deposit_address(self, depositAddress, currency=None):
        address = self.safe_string(depositAddress, 'address')
        tag = self.safe_string(depositAddress, 'destination_tag')
        currencyId = self.safe_string(depositAddress, 'currency_id')
        code = self.safe_currency_code(currencyId)
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': depositAddress,
        }

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency_id': currency['id'],
        }
        response = await self.privateGetDepositAddress(self.extend(request, params))
        #
        #     {
        #         "data":[
        #             {
        #                 "currency_id":"ETH",
        #                 "address":"0x12e2caf3c4051ba1146e612f532901a423a9898a",
        #                 "destination_tag":null
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        firstAddress = self.safe_value(data, 0)
        if firstAddress is None:
            raise InvalidAddress(self.id + ' fetchDepositAddress returned an empty response')
        return self.parse_deposit_address(firstAddress, currency)

    async def fetch_deposit_addresses(self, codes=None, params={}):
        await self.load_markets()
        request = {}
        if codes:
            currencyIds = []
            for i in range(0, len(codes)):
                currency = self.currency(codes[i])
                currencyIds.append(currency['id'])
            request['currency_id'] = ','.join(codes)
        response = await self.privateGetDepositAddress(self.extend(request, params))
        data = self.safe_value(response, 'data', [])
        return self.parse_deposit_addresses(data)

    async def withdraw(self, code, amount, address, tag=None, params={}):
        # In order to use self method
        # you need to allow API withdrawal from the API Settings Page, and
        # and register the list of withdrawal addresses and destination tags on the API Settings page
        # you can only withdraw to the registered addresses using the API
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        if tag is None:
            tag = ''
        request = {
            'currency_id': currency['id'],
            # 'platform_id': 'ETH',  # if omitted it will use the default platform for the currency
            'address': address,
            'destination_tag': tag,
            'amount': self.currency_to_precision(code, amount),
            # which currency to pay the withdrawal fees
            # only applicable for currencies that accepts multiple withdrawal fee options
            # 'fee_currency_id': 'ETH',  # if omitted it will use the default fee policy for each currency
            # whether the amount field includes fees
            # 'include_fee': False,  # makes sense only when fee_currency_id is equal to currency_id
        }
        response = await self.privatePostWithdrawal(self.extend(request, params))
        data = self.safe_value(response, 'data')
        return self.parse_transaction(data, currency)

    def parse_transaction(self, transaction, currency=None):
        id = self.safe_string(transaction, 'id')
        amount = self.safe_number(transaction, 'amount')
        address = self.safe_string(transaction, 'address')
        tag = self.safe_string(transaction, 'destination_tag')
        txid = self.safe_string(transaction, 'hash')
        timestamp = self.parse8601(self.safe_string(transaction, 'time'))
        type = self.safe_string(transaction, 'type')
        currencyId = self.safe_string(transaction, 'currency_id')
        code = self.safe_currency_code(currencyId)
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        feeCost = self.safe_number(transaction, 'fee')
        fee = None
        if feeCost is not None and feeCost != 0:
            fee = {
                'currency': code,
                'cost': feeCost,
            }
        return {
            'id': id,
            'currency': code,
            'amount': amount,
            'addressFrom': None,
            'address': address,
            'addressTo': address,
            'tagFrom': None,
            'tag': tag,
            'tagTo': tag,
            'status': status,
            'type': type,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': fee,
            'info': transaction,
        }

    def parse_transaction_status(self, status):
        statuses = {
            'requested': 'pending',
            'pending': 'pending',
            'confirming': 'pending',
            'confirmed': 'pending',
            'applying': 'pending',
            'done': 'ok',
            'cancelled': 'canceled',
            'cancelling': 'canceled',
        }
        return self.safe_string(statuses, status, status)

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

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api] + '/'
        query = self.omit(params, self.extract_params(path))
        if api == 'accounts':
            self.check_required_credentials()
            url += self.implode_params(path, params)
            auth = self.apiKey + ':' + self.secret
            auth64 = self.string_to_base64(auth)
            headers = {
                'Authorization': 'Basic ' + self.decode(auth64),
                'Content-Type': 'application/json',
            }
            if query:
                body = self.json(query)
        else:
            url += self.version + '/'
            if api == 'public':
                url += self.implode_params(path, params)
                if query:
                    url += '?' + self.urlencode(query)
            elif api == 'private':
                now = self.milliseconds()
                self.check_required_credentials()
                expires = self.safe_integer(self.options, 'expires')
                if (expires is None) or (expires < now):
                    raise AuthenticationError(self.id + ' access token expired, call signIn() method')
                accessToken = self.safe_string(self.options, 'accessToken')
                headers = {
                    'Authorization': 'Bearer ' + accessToken,
                }
                url += self.implode_params(path, params)
                if method == 'GET':
                    if query:
                        url += '?' + self.urlencode(query)
                elif query:
                    body = self.json(query)
                    headers['Content-Type'] = 'application/json'
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    async def sign_in(self, params={}):
        self.check_required_credentials()
        request = {
            'grant_type': 'client_credentials',  # the only supported value
        }
        response = await self.accountsPostToken(self.extend(request, params))
        #
        #     {
        #         access_token: '0ttDv/2hTTn3bLi8GP1gKaneiEQ6+0hOBenPrxNQt2s=',
        #         token_type: 'bearer',
        #         expires_in: 900
        #     }
        #
        expiresIn = self.safe_integer(response, 'expires_in')
        accessToken = self.safe_string(response, 'access_token')
        self.options['accessToken'] = accessToken
        self.options['expires'] = self.sum(self.milliseconds(), expiresIn * 1000)
        return response

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return  # fallback to default error handler
        if 'errorCode' in response:
            errorCode = self.safe_string(response, 'errorCode')
            message = self.safe_string(response, 'message')
            if errorCode is not None:
                feedback = self.id + ' ' + body
                self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
                self.throw_broadly_matched_exception(self.exceptions['exact'], errorCode, feedback)
                raise ExchangeError(feedback)
