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 hashlib
  from ccxt.base.errors import ExchangeError
  from ccxt.base.errors import AuthenticationError
  from ccxt.base.errors import AccountSuspended
  from ccxt.base.errors import BadSymbol
  from ccxt.base.errors import InsufficientFunds
  from ccxt.base.errors import InvalidOrder
  from ccxt.base.errors import OrderNotFound
  from ccxt.base.errors import OrderImmediatelyFillable
  from ccxt.base.errors import RateLimitExceeded
  from ccxt.base.errors import InvalidNonce
  
  
  class bitbay(Exchange):
  
      def describe(self):
          return self.deep_extend(super(bitbay, self).describe(), {
              'id': 'bitbay',
              'name': 'BitBay',
              'countries': ['MT', 'EU'],  # Malta
              'rateLimit': 1000,
              'has': {
                  'CORS': True,
                  'withdraw': True,
                  'fetchMyTrades': True,
                  'fetchOpenOrders': True,
                  'fetchOHLCV': True,
              },
              'timeframes': {
                  '1m': '60',
                  '3m': '180',
                  '5m': '300',
                  '15m': '900',
                  '30m': '1800',
                  '1h': '3600',
                  '2h': '7200',
                  '4h': '14400',
                  '6h': '21600',
                  '12h': '43200',
                  '1d': '86400',
                  '3d': '259200',
                  '1w': '604800',
              },
              'urls': {
                  'referral': 'https://auth.bitbay.net/ref/jHlbB4mIkdS1',
                  'logo': 'https://user-images.githubusercontent.com/1294454/27766132-978a7bd8-5ece-11e7-9540-bc96d1e9bbb8.jpg',
                  'www': 'https://bitbay.net',
                  'api': {
                      'public': 'https://bitbay.net/API/Public',
                      'private': 'https://bitbay.net/API/Trading/tradingApi.php',
                      'v1_01Public': 'https://api.bitbay.net/rest',
                      'v1_01Private': 'https://api.bitbay.net/rest',
                  },
                  'doc': [
                      'https://bitbay.net/public-api',
                      'https://bitbay.net/en/private-api',
                      'https://bitbay.net/account/tab-api',
                      'https://github.com/BitBayNet/API',
                      'https://docs.bitbay.net/v1.0.1-en/reference',
                  ],
                  'support': 'https://support.bitbay.net',
                  'fees': 'https://bitbay.net/en/fees',
              },
              'api': {
                  'public': {
                      'get': [
                          '{id}/all',
                          '{id}/market',
                          '{id}/orderbook',
                          '{id}/ticker',
                          '{id}/trades',
                      ],
                  },
                  'private': {
                      'post': [
                          'info',
                          'trade',
                          'cancel',
                          'orderbook',
                          'orders',
                          'transfer',
                          'withdraw',
                          'history',
                          'transactions',
                      ],
                  },
                  'v1_01Public': {
                      'get': [
                          'trading/ticker',
                          'trading/ticker/{symbol}',
                          'trading/stats',
                          'trading/orderbook/{symbol}',
                          'trading/transactions/{symbol}',
                          'trading/candle/history/{symbol}/{resolution}',
                      ],
                  },
                  'v1_01Private': {
                      'get': [
                          'payments/withdrawal/{detailId}',
                          'payments/deposit/{detailId}',
                          'trading/offer',
                          'trading/config/{symbol}',
                          'trading/history/transactions',
                          'balances/BITBAY/history',
                          'balances/BITBAY/balance',
                          'fiat_cantor/rate/{baseId}/{quoteId}',
                          'fiat_cantor/history',
                      ],
                      'post': [
                          'trading/offer/{symbol}',
                          'trading/config/{symbol}',
                          'balances/BITBAY/balance',
                          'balances/BITBAY/balance/transfer/{source}/{destination}',
                          'fiat_cantor/exchange',
                      ],
                      'delete': [
                          'trading/offer/{symbol}/{id}/{side}/{price}',
                      ],
                      'put': [
                          'balances/BITBAY/balance/{id}',
                      ],
                  },
              },
              'fees': {
                  'trading': {
                      'maker': 0.30 / 100,
                      'taker': 0.43 / 100,
                  },
                  'funding': {
                      'withdraw': {
                          'BTC': 0.0009,
                          'LTC': 0.005,
                          'ETH': 0.00126,
                          'LSK': 0.2,
                          'BCH': 0.0006,
                          'GAME': 0.005,
                          'DASH': 0.001,
                          'BTG': 0.0008,
                          'PLN': 4,
                          'EUR': 1.5,
                      },
                  },
              },
              'exceptions': {
                  '400': ExchangeError,  # At least one parameter wasn't set
                  '401': InvalidOrder,  # Invalid order type
                  '402': InvalidOrder,  # No orders with specified currencies
                  '403': InvalidOrder,  # Invalid payment currency name
                  '404': InvalidOrder,  # Error. Wrong transaction type
                  '405': InvalidOrder,  # Order with self id doesn't exist
                  '406': InsufficientFunds,  # No enough money or crypto
                  # code 407 not specified are not specified in their docs
                  '408': InvalidOrder,  # Invalid currency name
                  '501': AuthenticationError,  # Invalid public key
                  '502': AuthenticationError,  # Invalid sign
                  '503': InvalidNonce,  # Invalid moment parameter. Request time doesn't match current server time
                  '504': ExchangeError,  # Invalid method
                  '505': AuthenticationError,  # Key has no permission for self action
                  '506': AccountSuspended,  # Account locked. Please contact with customer service
                  # codes 507 and 508 are not specified in their docs
                  '509': ExchangeError,  # The BIC/SWIFT is required for self currency
                  '510': BadSymbol,  # Invalid market name
                  'FUNDS_NOT_SUFFICIENT': InsufficientFunds,
                  'OFFER_FUNDS_NOT_EXCEEDING_MINIMUMS': InvalidOrder,
                  'OFFER_NOT_FOUND': OrderNotFound,
                  'OFFER_WOULD_HAVE_BEEN_PARTIALLY_FILLED': OrderImmediatelyFillable,
                  'ACTION_LIMIT_EXCEEDED': RateLimitExceeded,
              },
          })
  
      async def fetch_markets(self, params={}):
          response = await self.v1_01PublicGetTradingTicker(params)
          #
          #     {
          #         status: 'Ok',
          #         items: {
          #             'BSV-USD': {
          #                 market: {
          #                     code: 'BSV-USD',
          #                     first: {currency: 'BSV', minOffer: '0.00035', scale: 8},
          #                     second: {currency: 'USD', minOffer: '5', scale: 2}
          #                 },
          #                 time: '1557569762154',
          #                 highestBid: '52.31',
          #                 lowestAsk: '62.99',
          #                 rate: '63',
          #                 previousRate: '51.21',
          #             },
          #         },
          #     }
          #
          result = []
          items = self.safe_value(response, 'items')
          keys = list(items.keys())
          for i in range(0, len(keys)):
              key = keys[i]
              item = items[key]
              market = self.safe_value(item, 'market', {})
              first = self.safe_value(market, 'first', {})
              second = self.safe_value(market, 'second', {})
              baseId = self.safe_string(first, 'currency')
              quoteId = self.safe_string(second, 'currency')
              id = baseId + quoteId
              base = self.safe_currency_code(baseId)
              quote = self.safe_currency_code(quoteId)
              symbol = base + '/' + quote
              precision = {
                  'amount': self.safe_integer(first, 'scale'),
                  'price': self.safe_integer(second, 'scale'),
              }
              # todo: check that the limits have ben interpreted correctly
              # todo: parse the fees page
              result.append({
                  'id': id,
                  'symbol': symbol,
                  'base': base,
                  'quote': quote,
                  'baseId': baseId,
                  'quoteId': quoteId,
                  'precision': precision,
                  'active': None,
                  'fee': None,
                  'limits': {
                      'amount': {
                          'min': self.safe_float(first, 'minOffer'),
                          'max': None,
                      },
                      'price': {
                          'min': None,
                          'max': None,
                      },
                      'cost': {
                          'min': self.safe_float(second, 'minOffer'),
                          'max': None,
                      },
                  },
                  'info': item,
              })
          return result
  
      async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          request = {}
          response = await self.v1_01PrivateGetTradingOffer(self.extend(request, params))
          items = self.safe_value(response, 'items', [])
          return self.parse_orders(items, None, since, limit, {'status': 'open'})
  
      def parse_order(self, order, market=None):
          #
          #     {
          #         market: 'ETH-EUR',
          #         offerType: 'Sell',
          #         id: '93d3657b-d616-11e9-9248-0242ac110005',
          #         currentAmount: '0.04',
          #         lockedAmount: '0.04',
          #         rate: '280',
          #         startAmount: '0.04',
          #         time: '1568372806924',
          #         postOnly: False,
          #         hidden: False,
          #         mode: 'limit',
          #         receivedAmount: '0.0',
          #         firstBalanceId: '5b816c3e-437c-4e43-9bef-47814ae7ebfc',
          #         secondBalanceId: 'ab43023b-4079-414c-b340-056e3430a3af'
          #     }
          #
          marketId = self.safe_string(order, 'market')
          symbol = None
          if marketId is not None:
              if marketId in self.markets_by_id:
                  market = self.markets_by_id[marketId]
              else:
                  baseId, quoteId = marketId.split('-')
                  base = self.safe_currency_code(baseId)
                  quote = self.safe_currency_code(quoteId)
                  symbol = base + '/' + quote
          if symbol is None:
              if market is not None:
                  symbol = market['symbol']
          timestamp = self.safe_integer(order, 'time')
          amount = self.safe_float(order, 'startAmount')
          remaining = self.safe_float(order, 'currentAmount')
          filled = None
          if amount is not None:
              if remaining is not None:
                  filled = max(0, amount - remaining)
          return {
              'id': self.safe_string(order, 'id'),
              'clientOrderId': None,
              'info': order,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'lastTradeTimestamp': None,
              'status': None,
              'symbol': symbol,
              'type': self.safe_string(order, 'mode'),
              'side': self.safe_string_lower(order, 'offerType'),
              'price': self.safe_float(order, 'rate'),
              'amount': amount,
              'cost': None,
              'filled': filled,
              'remaining': remaining,
              'average': None,
              'fee': None,
              'trades': None,
          }
  
      async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          request = {}
          if symbol:
              markets = [self.market_id(symbol)]
              request['markets'] = markets
          query = {'query': self.json(self.extend(request, params))}
          response = await self.v1_01PrivateGetTradingHistoryTransactions(query)
          #
          #     {
          #         status: 'Ok',
          #         totalRows: '67',
          #         items: [
          #             {
          #                 id: 'b54659a0-51b5-42a0-80eb-2ac5357ccee2',
          #                 market: 'BTC-EUR',
          #                 time: '1541697096247',
          #                 amount: '0.00003',
          #                 rate: '4341.44',
          #                 initializedBy: 'Sell',
          #                 wasTaker: False,
          #                 userAction: 'Buy',
          #                 offerId: 'bd19804a-6f89-4a69-adb8-eb078900d006',
          #                 commissionValue: null
          #             },
          #         ]
          #     }
          #
          items = self.safe_value(response, 'items')
          result = self.parse_trades(items, None, since, limit)
          if symbol is None:
              return result
          return self.filter_by_symbol(result, symbol)
  
      async def fetch_balance(self, params={}):
          await self.load_markets()
          response = await self.v1_01PrivateGetBalancesBITBAYBalance(params)
          balances = self.safe_value(response, 'balances')
          if balances is None:
              raise ExchangeError(self.id + ' empty balance response ' + self.json(response))
          result = {'info': response}
          for i in range(0, len(balances)):
              balance = balances[i]
              currencyId = self.safe_string(balance, 'currency')
              code = self.safe_currency_code(currencyId)
              account = self.account()
              account['used'] = self.safe_float(balance, 'lockedFunds')
              account['free'] = self.safe_float(balance, 'availableFunds')
              result[code] = account
          return self.parse_balance(result)
  
      async def fetch_order_book(self, symbol, limit=None, params={}):
          await self.load_markets()
          request = {
              'id': self.market_id(symbol),
          }
          orderbook = await self.publicGetIdOrderbook(self.extend(request, params))
          return self.parse_order_book(orderbook)
  
      async def fetch_ticker(self, symbol, params={}):
          await self.load_markets()
          request = {
              'id': self.market_id(symbol),
          }
          ticker = await self.publicGetIdTicker(self.extend(request, params))
          timestamp = self.milliseconds()
          baseVolume = self.safe_float(ticker, 'volume')
          vwap = self.safe_float(ticker, 'vwap')
          quoteVolume = None
          if baseVolume is not None and vwap is not None:
              quoteVolume = baseVolume * vwap
          last = self.safe_float(ticker, 'last')
          return {
              'symbol': symbol,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'high': self.safe_float(ticker, 'max'),
              'low': self.safe_float(ticker, 'min'),
              'bid': self.safe_float(ticker, 'bid'),
              'bidVolume': None,
              'ask': self.safe_float(ticker, 'ask'),
              'askVolume': None,
              'vwap': vwap,
              'open': None,
              'close': last,
              'last': last,
              'previousClose': None,
              'change': None,
              'percentage': None,
              'average': self.safe_float(ticker, 'average'),
              'baseVolume': baseVolume,
              'quoteVolume': quoteVolume,
              'info': ticker,
          }
  
      async def fetch_ledger(self, code=None, since=None, limit=None, params={}):
          balanceCurrencies = []
          if code is not None:
              currency = self.currency(code)
              balanceCurrencies.append(currency['id'])
          request = {
              'balanceCurrencies': balanceCurrencies,
          }
          if since is not None:
              request['fromTime'] = since
          if limit is not None:
              request['limit'] = limit
          request = self.extend(request, params)
          response = await self.v1_01PrivateGetBalancesBITBAYHistory({'query': self.json(request)})
          items = response['items']
          return self.parse_ledger(items, None, since, limit)
  
      def parse_ledger_entry(self, item, currency=None):
          #
          #    FUNDS_MIGRATION
          #    {
          #      "historyId": "84ea7a29-7da5-4de5-b0c0-871e83cad765",
          #      "balance": {
          #        "id": "821ec166-cb88-4521-916c-f4eb44db98df",
          #        "currency": "LTC",
          #        "type": "CRYPTO",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "LTC"
          #      },
          #      "detailId": null,
          #      "time": 1506128252968,
          #      "type": "FUNDS_MIGRATION",
          #      "value": 0.0009957,
          #      "fundsBefore": {"total": 0, "available": 0, "locked": 0},
          #      "fundsAfter": {"total": 0.0009957, "available": 0.0009957, "locked": 0},
          #      "change": {"total": 0.0009957, "available": 0.0009957, "locked": 0}
          #    }
          #
          #    CREATE_BALANCE
          #    {
          #      "historyId": "d0fabd8d-9107-4b5e-b9a6-3cab8af70d49",
          #      "balance": {
          #        "id": "653ffcf2-3037-4ebe-8e13-d5ea1a01d60d",
          #        "currency": "BTG",
          #        "type": "CRYPTO",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "BTG"
          #      },
          #      "detailId": null,
          #      "time": 1508895244751,
          #      "type": "CREATE_BALANCE",
          #      "value": 0,
          #      "fundsBefore": {"total": null, "available": null, "locked": null},
          #      "fundsAfter": {"total": 0, "available": 0, "locked": 0},
          #      "change": {"total": 0, "available": 0, "locked": 0}
          #    }
          #
          #    BITCOIN_GOLD_FORK
          #    {
          #      "historyId": "2b4d52d3-611c-473d-b92c-8a8d87a24e41",
          #      "balance": {
          #        "id": "653ffcf2-3037-4ebe-8e13-d5ea1a01d60d",
          #        "currency": "BTG",
          #        "type": "CRYPTO",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "BTG"
          #      },
          #      "detailId": null,
          #      "time": 1508895244778,
          #      "type": "BITCOIN_GOLD_FORK",
          #      "value": 0.00453512,
          #      "fundsBefore": {"total": 0, "available": 0, "locked": 0},
          #      "fundsAfter": {"total": 0.00453512, "available": 0.00453512, "locked": 0},
          #      "change": {"total": 0.00453512, "available": 0.00453512, "locked": 0}
          #    }
          #
          #    ADD_FUNDS
          #    {
          #      "historyId": "3158236d-dae5-4a5d-81af-c1fa4af340fb",
          #      "balance": {
          #        "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7",
          #        "currency": "BTC",
          #        "type": "CRYPTO",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "BTC"
          #      },
          #      "detailId": "8e83a960-e737-4380-b8bb-259d6e236faa",
          #      "time": 1520631178816,
          #      "type": "ADD_FUNDS",
          #      "value": 0.628405,
          #      "fundsBefore": {"total": 0.00453512, "available": 0.00453512, "locked": 0},
          #      "fundsAfter": {"total": 0.63294012, "available": 0.63294012, "locked": 0},
          #      "change": {"total": 0.628405, "available": 0.628405, "locked": 0}
          #    }
          #
          #    TRANSACTION_PRE_LOCKING
          #    {
          #      "historyId": "e7d19e0f-03b3-46a8-bc72-dde72cc24ead",
          #      "balance": {
          #        "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7",
          #        "currency": "BTC",
          #        "type": "CRYPTO",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "BTC"
          #      },
          #      "detailId": null,
          #      "time": 1520706403868,
          #      "type": "TRANSACTION_PRE_LOCKING",
          #      "value": -0.1,
          #      "fundsBefore": {"total": 0.63294012, "available": 0.63294012, "locked": 0},
          #      "fundsAfter": {"total": 0.63294012, "available": 0.53294012, "locked": 0.1},
          #      "change": {"total": 0, "available": -0.1, "locked": 0.1}
          #    }
          #
          #    TRANSACTION_POST_OUTCOME
          #    {
          #      "historyId": "c4010825-231d-4a9c-8e46-37cde1f7b63c",
          #      "balance": {
          #        "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7",
          #        "currency": "BTC",
          #        "type": "CRYPTO",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "BTC"
          #      },
          #      "detailId": "bf2876bc-b545-4503-96c8-ef4de8233876",
          #      "time": 1520706404032,
          #      "type": "TRANSACTION_POST_OUTCOME",
          #      "value": -0.01771415,
          #      "fundsBefore": {"total": 0.63294012, "available": 0.53294012, "locked": 0.1},
          #      "fundsAfter": {"total": 0.61522597, "available": 0.53294012, "locked": 0.08228585},
          #      "change": {"total": -0.01771415, "available": 0, "locked": -0.01771415}
          #    }
          #
          #    TRANSACTION_POST_INCOME
          #    {
          #      "historyId": "7f18b7af-b676-4125-84fd-042e683046f6",
          #      "balance": {
          #        "id": "ab43023b-4079-414c-b340-056e3430a3af",
          #        "currency": "EUR",
          #        "type": "FIAT",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "EUR"
          #      },
          #      "detailId": "f5fcb274-0cc7-4385-b2d3-bae2756e701f",
          #      "time": 1520706404035,
          #      "type": "TRANSACTION_POST_INCOME",
          #      "value": 628.78,
          #      "fundsBefore": {"total": 0, "available": 0, "locked": 0},
          #      "fundsAfter": {"total": 628.78, "available": 628.78, "locked": 0},
          #      "change": {"total": 628.78, "available": 628.78, "locked": 0}
          #    }
          #
          #    TRANSACTION_COMMISSION_OUTCOME
          #    {
          #      "historyId": "843177fa-61bc-4cbf-8be5-b029d856c93b",
          #      "balance": {
          #        "id": "ab43023b-4079-414c-b340-056e3430a3af",
          #        "currency": "EUR",
          #        "type": "FIAT",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "EUR"
          #      },
          #      "detailId": "f5fcb274-0cc7-4385-b2d3-bae2756e701f",
          #      "time": 1520706404050,
          #      "type": "TRANSACTION_COMMISSION_OUTCOME",
          #      "value": -2.71,
          #      "fundsBefore": {"total": 766.06, "available": 766.06, "locked": 0},
          #      "fundsAfter": {"total": 763.35,"available": 763.35, "locked": 0},
          #      "change": {"total": -2.71, "available": -2.71, "locked": 0}
          #    }
          #
          #    TRANSACTION_OFFER_COMPLETED_RETURN
          #    {
          #      "historyId": "cac69b04-c518-4dc5-9d86-e76e91f2e1d2",
          #      "balance": {
          #        "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7",
          #        "currency": "BTC",
          #        "type": "CRYPTO",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "BTC"
          #      },
          #      "detailId": null,
          #      "time": 1520714886425,
          #      "type": "TRANSACTION_OFFER_COMPLETED_RETURN",
          #      "value": 0.00000196,
          #      "fundsBefore": {"total": 0.00941208, "available": 0.00941012, "locked": 0.00000196},
          #      "fundsAfter": {"total": 0.00941208, "available": 0.00941208, "locked": 0},
          #      "change": {"total": 0, "available": 0.00000196, "locked": -0.00000196}
          #    }
          #
          #    WITHDRAWAL_LOCK_FUNDS
          #    {
          #      "historyId": "03de2271-66ab-4960-a786-87ab9551fc14",
          #      "balance": {
          #        "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7",
          #        "currency": "BTC",
          #        "type": "CRYPTO",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "BTC"
          #      },
          #      "detailId": "6ad3dc72-1d6d-4ec2-8436-ca43f85a38a6",
          #      "time": 1522245654481,
          #      "type": "WITHDRAWAL_LOCK_FUNDS",
          #      "value": -0.8,
          #      "fundsBefore": {"total": 0.8, "available": 0.8, "locked": 0},
          #      "fundsAfter": {"total": 0.8, "available": 0, "locked": 0.8},
          #      "change": {"total": 0, "available": -0.8, "locked": 0.8}
          #    }
          #
          #    WITHDRAWAL_SUBTRACT_FUNDS
          #    {
          #      "historyId": "b0308c89-5288-438d-a306-c6448b1a266d",
          #      "balance": {
          #        "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7",
          #        "currency": "BTC",
          #        "type": "CRYPTO",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "BTC"
          #      },
          #      "detailId": "6ad3dc72-1d6d-4ec2-8436-ca43f85a38a6",
          #      "time": 1522246526186,
          #      "type": "WITHDRAWAL_SUBTRACT_FUNDS",
          #      "value": -0.8,
          #      "fundsBefore": {"total": 0.8, "available": 0, "locked": 0.8},
          #      "fundsAfter": {"total": 0, "available": 0, "locked": 0},
          #      "change": {"total": -0.8, "available": 0, "locked": -0.8}
          #    }
          #
          #    TRANSACTION_OFFER_ABORTED_RETURN
          #    {
          #      "historyId": "b1a3c075-d403-4e05-8f32-40512cdd88c0",
          #      "balance": {
          #        "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7",
          #        "currency": "BTC",
          #        "type": "CRYPTO",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "BTC"
          #      },
          #      "detailId": null,
          #      "time": 1522512298662,
          #      "type": "TRANSACTION_OFFER_ABORTED_RETURN",
          #      "value": 0.0564931,
          #      "fundsBefore": {"total": 0.44951311, "available": 0.39302001, "locked": 0.0564931},
          #      "fundsAfter": {"total": 0.44951311, "available": 0.44951311, "locked": 0},
          #      "change": {"total": 0, "available": 0.0564931, "locked": -0.0564931}
          #    }
          #
          #    WITHDRAWAL_UNLOCK_FUNDS
          #    {
          #      "historyId": "0ed569a2-c330-482e-bb89-4cb553fb5b11",
          #      "balance": {
          #        "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7",
          #        "currency": "BTC",
          #        "type": "CRYPTO",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "BTC"
          #      },
          #      "detailId": "0c7be256-c336-4111-bee7-4eb22e339700",
          #      "time": 1527866360785,
          #      "type": "WITHDRAWAL_UNLOCK_FUNDS",
          #      "value": 0.05045,
          #      "fundsBefore": {"total": 0.86001578, "available": 0.80956578, "locked": 0.05045},
          #      "fundsAfter": {"total": 0.86001578, "available": 0.86001578, "locked": 0},
          #      "change": {"total": 0, "available": 0.05045, "locked": -0.05045}
          #    }
          #
          #    TRANSACTION_COMMISSION_RETURN
          #    {
          #      "historyId": "07c89c27-46f1-4d7a-8518-b73798bf168a",
          #      "balance": {
          #        "id": "ab43023b-4079-414c-b340-056e3430a3af",
          #        "currency": "EUR",
          #        "type": "FIAT",
          #        "userId": "a34d361d-7bad-49c1-888e-62473b75d877",
          #        "name": "EUR"
          #      },
          #      "detailId": null,
          #      "time": 1528304043063,
          #      "type": "TRANSACTION_COMMISSION_RETURN",
          #      "value": 0.6,
          #      "fundsBefore": {"total": 0, "available": 0, "locked": 0},
          #      "fundsAfter": {"total": 0.6, "available": 0.6, "locked": 0},
          #      "change": {"total": 0.6, "available": 0.6, "locked": 0}
          #    }
          #
          timestamp = self.safe_integer(item, 'time')
          balance = self.safe_value(item, 'balance', {})
          currencyId = self.safe_string(balance, 'currency')
          code = self.safe_currency_code(currencyId)
          change = self.safe_value(item, 'change', {})
          amount = self.safe_float(change, 'total')
          direction = 'in'
          if amount < 0:
              direction = 'out'
              amount = -amount
          id = self.safe_string(item, 'historyId')
          # there are 2 undocumented api calls: (v1_01PrivateGetPaymentsDepositDetailId and v1_01PrivateGetPaymentsWithdrawalDetailId)
          # that can be used to enrich the transfers with txid, address etc(you need to use info.detailId as a parameter)
          referenceId = self.safe_string(item, 'detailId')
          type = self.parse_ledger_entry_type(self.safe_string(item, 'type'))
          fundsBefore = self.safe_value(item, 'fundsBefore', {})
          before = self.safe_float(fundsBefore, 'total')
          fundsAfter = self.safe_value(item, 'fundsAfter', {})
          after = self.safe_float(fundsAfter, 'total')
          return {
              'info': item,
              'id': id,
              'direction': direction,
              'account': None,
              'referenceId': referenceId,
              'referenceAccount': None,
              'type': type,
              'currency': code,
              'amount': amount,
              'before': before,
              'after': after,
              'status': 'ok',
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'fee': None,
          }
  
      def parse_ledger_entry_type(self, type):
          types = {
              'ADD_FUNDS': 'transaction',
              'BITCOIN_GOLD_FORK': 'transaction',
              'CREATE_BALANCE': 'transaction',
              'FUNDS_MIGRATION': 'transaction',
              'WITHDRAWAL_LOCK_FUNDS': 'transaction',
              'WITHDRAWAL_SUBTRACT_FUNDS': 'transaction',
              'WITHDRAWAL_UNLOCK_FUNDS': 'transaction',
              'TRANSACTION_COMMISSION_OUTCOME': 'fee',
              'TRANSACTION_COMMISSION_RETURN': 'fee',
              'TRANSACTION_OFFER_ABORTED_RETURN': 'trade',
              'TRANSACTION_OFFER_COMPLETED_RETURN': 'trade',
              'TRANSACTION_POST_INCOME': 'trade',
              'TRANSACTION_POST_OUTCOME': 'trade',
              'TRANSACTION_PRE_LOCKING': 'trade',
          }
          return self.safe_string(types, type, type)
  
      def parse_ohlcv(self, ohlcv, market=None, timeframe='1m', since=None, limit=None):
          # [
          #     '1582399800000',
          #     {
          #         o: '0.0001428',
          #         c: '0.0001428',
          #         h: '0.0001428',
          #         l: '0.0001428',
          #         v: '4',
          #         co: '1'
          #     }
          # ]
          return [
              int(ohlcv[0]),
              self.safe_float(ohlcv[1], 'o'),
              self.safe_float(ohlcv[1], 'h'),
              self.safe_float(ohlcv[1], 'l'),
              self.safe_float(ohlcv[1], 'c'),
              self.safe_float(ohlcv[1], 'v'),
          ]
  
      async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          tradingSymbol = market['baseId'] + '-' + market['quoteId']
          request = {
              'symbol': tradingSymbol,
              'resolution': self.timeframes[timeframe],
              # 'from': 1574709092000,  # unix timestamp in milliseconds, required
              # 'to': 1574709092000,  # unix timestamp in milliseconds, required
          }
          if limit is None:
              limit = 100
          duration = self.parse_timeframe(timeframe)
          timerange = limit * duration * 1000
          if since is None:
              request['to'] = self.milliseconds()
              request['from'] = request['to'] - timerange
          else:
              request['from'] = int(since)
              request['to'] = self.sum(request['from'], timerange)
          response = await self.v1_01PublicGetTradingCandleHistorySymbolResolution(self.extend(request, params))
          ohlcvs = self.safe_value(response, 'items', [])
          return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
  
      def parse_trade(self, trade, market=None):
          #
          # createOrder trades
          #
          #     {
          #         "rate": "0.02195928",
          #         "amount": "0.00167952"
          #     }
          #
          # fetchMyTrades(private)
          #
          #     {
          #         amount: "0.29285199",
          #         commissionValue: "0.00125927",
          #         id: "11c8203a-a267-11e9-b698-0242ac110007",
          #         initializedBy: "Buy",
          #         market: "ETH-EUR",
          #         offerId: "11c82038-a267-11e9-b698-0242ac110007",
          #         rate: "277",
          #         time: "1562689917517",
          #         userAction: "Buy",
          #         wasTaker: True,
          #     }
          #
          # fetchTrades(public)
          #
          #     {
          #          id: 'df00b0da-e5e0-11e9-8c19-0242ac11000a',
          #          t: '1570108958831',
          #          a: '0.04776653',
          #          r: '0.02145854',
          #          ty: 'Sell'
          #     }
          #
          timestamp = self.safe_integer_2(trade, 'time', 't')
          userAction = self.safe_string(trade, 'userAction')
          side = 'buy' if (userAction == 'Buy') else 'sell'
          wasTaker = self.safe_value(trade, 'wasTaker')
          takerOrMaker = None
          if wasTaker is not None:
              takerOrMaker = 'taker' if wasTaker else 'maker'
          price = self.safe_float_2(trade, 'rate', 'r')
          amount = self.safe_float_2(trade, 'amount', 'a')
          cost = None
          if amount is not None:
              if price is not None:
                  cost = price * amount
          feeCost = self.safe_float(trade, 'commissionValue')
          marketId = self.safe_string(trade, 'market')
          base = None
          quote = None
          symbol = None
          if marketId is not None:
              if marketId in self.markets_by_id:
                  market = self.markets_by_id[marketId]
                  symbol = market['symbol']
                  base = market['base']
                  quote = market['quote']
              else:
                  baseId, quoteId = marketId.split('-')
                  base = self.safe_currency_code(baseId)
                  quote = self.safe_currency_code(quoteId)
                  symbol = base + '/' + quote
          if market is not None:
              if symbol is None:
                  symbol = market['symbol']
              if base is None:
                  base = market['base']
          fee = None
          if feeCost is not None:
              feeCcy = base if (side == 'buy') else quote
              fee = {
                  'currency': feeCcy,
                  'cost': feeCost,
              }
          order = self.safe_string(trade, 'offerId')
          # todo: check self logic
          type = None
          if order is not None:
              type = 'limit' if order else 'market'
          return {
              'id': self.safe_string(trade, 'id'),
              'order': order,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'symbol': symbol,
              'type': type,
              'side': side,
              'price': price,
              'amount': amount,
              'cost': cost,
              'takerOrMaker': takerOrMaker,
              'fee': fee,
              'info': trade,
          }
  
      async def fetch_trades(self, symbol, since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          tradingSymbol = market['baseId'] + '-' + market['quoteId']
          request = {
              'symbol': tradingSymbol,
          }
          if since is not None:
              request['fromTime'] = since - 1  # result does not include exactly `since` time therefore decrease by 1
          if limit is not None:
              request['limit'] = limit  # default - 10, max - 300
          response = await self.v1_01PublicGetTradingTransactionsSymbol(self.extend(request, params))
          items = self.safe_value(response, 'items')
          return self.parse_trades(items, symbol, since, limit)
  
      async def create_order(self, symbol, type, side, amount, price=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          tradingSymbol = market['baseId'] + '-' + market['quoteId']
          request = {
              'symbol': tradingSymbol,
              'offerType': side,
              'amount': amount,
              'mode': type,
          }
          if type == 'limit':
              request['rate'] = price
          response = await self.v1_01PrivatePostTradingOfferSymbol(self.extend(request, params))
          #
          # unfilled(open order)
          #
          #     {
          #         status: 'Ok',
          #         completed: False,  # can deduce status from here
          #         offerId: 'ce9cc72e-d61c-11e9-9248-0242ac110005',
          #         transactions: [],  # can deduce order info from here
          #     }
          #
          # filled(closed order)
          #
          #     {
          #         "status": "Ok",
          #         "offerId": "942a4a3e-e922-11e9-8c19-0242ac11000a",
          #         "completed": True,
          #         "transactions": [
          #           {
          #             "rate": "0.02195928",
          #             "amount": "0.00167952"
          #           },
          #           {
          #             "rate": "0.02195928",
          #             "amount": "0.00167952"
          #           },
          #           {
          #             "rate": "0.02196207",
          #             "amount": "0.27704177"
          #           }
          #         ]
          #     }
          #
          # partially-filled(open order)
          #
          #     {
          #         "status": "Ok",
          #         "offerId": "d0ebefab-f4d7-11e9-8c19-0242ac11000a",
          #         "completed": False,
          #         "transactions": [
          #           {
          #             "rate": "0.02106404",
          #             "amount": "0.0019625"
          #           },
          #           {
          #             "rate": "0.02106404",
          #             "amount": "0.0019625"
          #           },
          #           {
          #             "rate": "0.02105901",
          #             "amount": "0.00975256"
          #           }
          #         ]
          #     }
          #
          timestamp = self.milliseconds()  # the real timestamp is missing in the response
          id = self.safe_string(response, 'offerId')
          completed = self.safe_value(response, 'completed', False)
          status = 'closed' if completed else 'open'
          filled = 0
          cost = None
          transactions = self.safe_value(response, 'transactions')
          trades = None
          if transactions is not None:
              trades = self.parse_trades(transactions, market, None, None, {
                  'timestamp': timestamp,
                  'datetime': self.iso8601(timestamp),
                  'symbol': symbol,
                  'side': side,
                  'type': type,
                  'orderId': id,
              })
              cost = 0
              for i in range(0, len(trades)):
                  filled = self.sum(filled, trades[i]['amount'])
                  cost = self.sum(cost, trades[i]['cost'])
          remaining = amount - filled
          return {
              'id': id,
              'info': response,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'lastTradeTimestamp': None,
              'status': status,
              'symbol': symbol,
              'type': type,
              'side': side,
              'price': float(price),
              'amount': float(amount),
              'cost': cost,
              'filled': filled,
              'remaining': remaining,
              'average': None,
              'fee': None,
              'trades': trades,
              'clientOrderId': None,
          }
  
      async def cancel_order(self, id, symbol=None, params={}):
          side = self.safe_string(params, 'side')
          if side is None:
              raise ExchangeError(self.id + ' cancelOrder() requires a `side` parameter("buy" or "sell")')
          price = self.safe_value(params, 'price')
          if price is None:
              raise ExchangeError(self.id + ' cancelOrder() requires a `price` parameter(float or string)')
          await self.load_markets()
          market = self.market(symbol)
          tradingSymbol = market['baseId'] + '-' + market['quoteId']
          request = {
              'symbol': tradingSymbol,
              'id': id,
              'side': side,
              'price': price,
          }
          # {status: 'Fail', errors: ['NOT_RECOGNIZED_OFFER_TYPE']}  -- if required params are missing
          # {status: 'Ok', errors: []}
          return self.v1_01PrivateDeleteTradingOfferSymbolIdSidePrice(self.extend(request, params))
  
      def is_fiat(self, currency):
          fiatCurrencies = {
              'USD': True,
              'EUR': True,
              'PLN': True,
          }
          return self.safe_value(fiatCurrencies, currency, False)
  
      async def withdraw(self, code, amount, address, tag=None, params={}):
          self.check_address(address)
          await self.load_markets()
          method = None
          currency = self.currency(code)
          request = {
              'currency': currency['id'],
              'quantity': amount,
          }
          if self.is_fiat(code):
              method = 'privatePostWithdraw'
              # request['account'] = params['account']  # they demand an account number
              # request['express'] = params['express']  # whatever it means, they don't explain
              # request['bic'] = ''
          else:
              method = 'privatePostTransfer'
              if tag is not None:
                  address += '?dt=' + str(tag)
              request['address'] = address
          response = await getattr(self, method)(self.extend(request, params))
          return {
              'info': response,
              'id': None,
          }
  
      def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
          url = self.urls['api'][api]
          if api == 'public':
              query = self.omit(params, self.extract_params(path))
              url += '/' + self.implode_params(path, params) + '.json'
              if query:
                  url += '?' + self.urlencode(query)
          elif api == 'v1_01Public':
              query = self.omit(params, self.extract_params(path))
              url += '/' + self.implode_params(path, params)
              if query:
                  url += '?' + self.urlencode(query)
          elif api == 'v1_01Private':
              self.check_required_credentials()
              query = self.omit(params, self.extract_params(path))
              url += '/' + self.implode_params(path, params)
              nonce = str(self.milliseconds())
              payload = None
              if method != 'POST':
                  if query:
                      url += '?' + self.urlencode(query)
                  payload = self.apiKey + nonce
              elif body is None:
                  body = self.json(query)
                  payload = self.apiKey + nonce + body
              headers = {
                  'Request-Timestamp': nonce,
                  'Operation-Id': self.uuid(),
                  'API-Key': self.apiKey,
                  'API-Hash': self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha512),
                  'Content-Type': 'application/json',
              }
          else:
              self.check_required_credentials()
              body = self.urlencode(self.extend({
                  'method': path,
                  'moment': self.nonce(),
              }, params))
              headers = {
                  'Content-Type': 'application/x-www-form-urlencoded',
                  'API-Key': self.apiKey,
                  'API-Hash': self.hmac(self.encode(body), self.encode(self.secret), hashlib.sha512),
              }
          return {'url': url, 'method': method, 'body': body, 'headers': headers}
  
      def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
          if response is None:
              return  # fallback to default error handler
          if 'code' in response:
              #
              # bitbay returns the integer 'success': 1 key from their private API
              # or an integer 'code' value from 0 to 510 and an error message
              #
              #      {'success': 1, ...}
              #      {'code': 502, 'message': 'Invalid sign'}
              #      {'code': 0, 'message': 'offer funds not exceeding minimums'}
              #
              #      400 At least one parameter wasn't set
              #      401 Invalid order type
              #      402 No orders with specified currencies
              #      403 Invalid payment currency name
              #      404 Error. Wrong transaction type
              #      405 Order with self id doesn't exist
              #      406 No enough money or crypto
              #      408 Invalid currency name
              #      501 Invalid public key
              #      502 Invalid sign
              #      503 Invalid moment parameter. Request time doesn't match current server time
              #      504 Invalid method
              #      505 Key has no permission for self action
              #      506 Account locked. Please contact with customer service
              #      509 The BIC/SWIFT is required for self currency
              #      510 Invalid market name
              #
              code = self.safe_string(response, 'code')  # always an integer
              feedback = self.id + ' ' + body
              self.throw_exactly_matched_exception(self.exceptions, code, feedback)
              raise ExchangeError(feedback)
          elif 'status' in response:
              #
              #      {"status":"Fail","errors":["OFFER_FUNDS_NOT_EXCEEDING_MINIMUMS"]}
              #
              status = self.safe_string(response, 'status')
              if status == 'Fail':
                  errors = self.safe_value(response, 'errors')
                  feedback = self.id + ' ' + body
                  for i in range(0, len(errors)):
                      error = errors[i]
                      self.throw_exactly_matched_exception(self.exceptions, error, feedback)
                  raise ExchangeError(feedback)
  