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

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

from ccxt.base.exchange import Exchange
import 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


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
                    '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': {
                'BTCBEAR': 'BEAR',
                'BTCBULL': 'BULL',
                'CBC': 'CryptoBharatCoin',
                'HBC': 'Hybrid Bank Cash',
                'UNI': 'UNICORN Token',
            },
        })

    def fetch_markets(self, params={}):
        response = 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_integer(market, 'quantity_precision')
            costPrecision = self.safe_integer(market, 'cost_precision')
            precision = {
                'amount': 1 / math.pow(10, amountPrecision),
                'price': self.safe_float(market, 'price_increment'),
                'cost': 1 / math.pow(10, costPrecision),
            }
            takerFeeRate = self.safe_float(market, 'taker_fee_rate')
            makerFeeRate = self.safe_float(market, 'maker_fee_rate')
            result.append({
                'id': id,
                'info': market,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': active,
                'precision': precision,
                'taker': takerFeeRate / 100,
                'maker': makerFeeRate / 100,
                'limits': {
                    'amount': {
                        'min': self.safe_float(market, 'min_quantity'),
                        'max': self.safe_float(market, 'max_quantity'),
                    },
                    'price': {
                        'min': self.safe_float(market, 'min_price'),
                        'max': self.safe_float(market, 'max_price'),
                    },
                    'cost': {
                        'min': self.safe_float(market, 'min_cost'),
                        'max': self.safe_float(market, 'max_cost'),
                    },
                },
            })
        return result

    def fetch_currencies(self, params={}):
        response = 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', {})
            withdrawalFeesByPriority = self.sort_by(withdrawalFees, 'priority')
            withdrawalFee = self.safe_value(withdrawalFeesByPriority, 0, {})
            fee = self.safe_float(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),
                    },
                    'price': {
                        'min': math.pow(10, -precision),
                        'max': math.pow(10, precision),
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                    'deposit': {
                        'min': self.safe_float(platform, 'min_deposit_amount'),
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_float(platform, 'min_withdrawal_amount'),
                        'max': None,
                    },
                },
            }
        return result

    def fetch_balance(self, params={}):
        self.load_markets()
        response = self.privateGetBalance(params)
        #
        #     {
        #         data: [
        #             {
        #                 "currency_id":"XRP",
        #                 "total":"100",
        #                 "available":"0",
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data')
        result = {'info': 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_float(balance, 'total')
            account['free'] = self.safe_float(balance, 'available')
            result[code] = account
        return self.parse_balance(result)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market_id': market['id'],
        }
        response = 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, None, 'buy', 'sell', 'price', 'quantity')

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        request = {}
        if symbols is not None:
            marketIds = self.market_ids(symbols)
            request['market_ids'] = ','.join(marketIds)
        response = 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)

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

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market_ids': market['id'],
        }
        response = 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_float(ticker, 'last')
        change = self.safe_float(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_float(ticker, 'base_volume')
        quoteVolume = self.safe_float(ticker, 'quote_volume')
        vwap = self.vwap(baseVolume, quoteVolume)
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': 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,
        }

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        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 = 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)

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        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 = 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')
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'quantity')
        cost = None
        if price is not None:
            if amount is not None:
                cost = price * amount
        orderId = self.safe_string(trade, 'order_id')
        feeCost = self.safe_float(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,
        }

    def fetch_time(self, params={}):
        response = 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)

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        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 = 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_float(ohlcv, 'open'),
            self.safe_float(ohlcv, 'high'),
            self.safe_float(ohlcv, 'low'),
            self.safe_float(ohlcv, 'close'),
            self.safe_float(ohlcv, 'base_volume'),
        ]

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        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 = self.privateGetOpenOrder(self.extend(request, params))
        data = self.safe_value(response, 'data')
        return self.parse_orders(data, market, since, limit)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        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 = self.privateGetOrderHistory(self.extend(request, params))
        data = self.safe_value(response, 'data')
        return self.parse_orders(data, market, since, limit)

    def fetch_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        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 = 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_float(order, 'limit_price')
        filled = self.safe_float(order, 'filled_quantity')
        remaining = self.safe_float(order, 'open_quantity')
        canceledAmount = self.safe_float(order, 'cancelled_quantity')
        if canceledAmount is not None:
            remaining = self.sum(remaining, canceledAmount)
        amount = self.safe_float(order, 'quantity', self.sum(filled, remaining))
        cost = self.safe_float_2(order, 'filled_cost', 'cost')
        if type == 'market':
            price = None
        average = None
        if filled is not None:
            if cost is None:
                if price is not None:
                    cost = price * filled
            if cost is not None:
                if filled > 0:
                    average = cost / filled
        clientOrderId = self.safe_string(order, 'client_order_id')
        if clientOrderId == '':
            clientOrderId = None
        timeInForce = self.safe_string_upper(order, 'time_in_force')
        return {
            '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': average,
            '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)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        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_float(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 = 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

    def cancel_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market_id': market['id'],
            'order_id': id,
        }
        response = 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,
        }

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency_id': currency['id'],
        }
        response = 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)

    def fetch_deposit_addresses(self, codes=None, params={}):
        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 = self.privateGetDepositAddress(self.extend(request, params))
        data = self.safe_value(response, 'data', [])
        return self.parse_deposit_addresses(data)

    def parse_deposit_addresses(self, addresses):
        result = {}
        for i in range(0, len(addresses)):
            address = self.parse_deposit_address(addresses[i])
            code = address['currency']
            result[code] = address
        return result

    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)
        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 = 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_float(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_float(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}

    def sign_in(self, params={}):
        self.check_required_credentials()
        request = {
            'grant_type': 'client_credentials',  # the only supported value
        }
        response = 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)
