text: # -*- 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': self.parse_number('0.002'),
                      'taker': self.parse_number('0.002'),
                  },
              },
              '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',
                  'BCC': 'BCC',
                  'BDP': 'BidiPass',
                  'BTCBEAR': 'BEAR',
                  'BTCBULL': 'BULL',
                  'CBC': 'CryptoBharatCoin',
                  'EPS': 'Epanus',  # conflict with EPS Ellipsis https://github.com/ccxt/ccxt/issues/8909
                  'HBC': 'Hybrid Bank Cash',
                  'ORC': 'Oracle System',
                  'SOC': 'Soda Coin',
                  'UNI': 'UNICORN Token',
                  'UNISWAP': 'UNI',
              },
          })
  
      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)
  
      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 = int(math.floor(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.number_to_string(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)
  