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

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

from ccxt.base.exchange import Exchange
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InvalidAddress
from ccxt.base.errors import OrderNotFound
from ccxt.base.precise import Precise


class therock(Exchange):

    def describe(self):
        return self.deep_extend(super(therock, self).describe(), {
            'id': 'therock',
            'name': 'TheRockTrading',
            'countries': ['MT'],
            # 10 requests per second => 1000ms / 10 => 100 ms between requests(all endpoints)
            'rateLimit': 100,
            'version': 'v1',
            'has': {
                'CORS': None,
                'spot': True,
                'margin': None,  # has but unimplemented
                'swap': False,
                'future': False,
                'option': False,
                'cancelOrder': True,
                'createOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchDeposits': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchLedger': True,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': True,
                'fetchTransactions': 'emulated',
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawals': True,
                'transfer': False,
                'withdraw': True,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766869-75057fa2-5ee9-11e7-9a6f-13e641fa4707.jpg',
                'api': 'https://api.therocktrading.com',
                'www': 'https://therocktrading.com',
                'doc': [
                    'https://api.therocktrading.com/doc/v1/index.html',
                    'https://api.therocktrading.com/doc/',
                ],
            },
            'api': {
                'public': {
                    'get': {
                        'funds': 1,
                        'funds/{id}': 1,
                        'funds/{id}/orderbook': 1,
                        'funds/{id}/ticker': 1,
                        'funds/{id}/trades': 1,
                        'funds/tickers': 1,
                    },
                },
                'private': {
                    'get': {
                        'balances': 1,
                        'balances/{id}': 1,
                        'discounts': 1,
                        'discounts/{id}': 1,
                        'funds': 1,
                        'funds/{id}': 1,
                        'funds/{id}/trades': 1,
                        'funds/{fund_id}/orders': 1,
                        'funds/{fund_id}/orders/{id}': 1,
                        'funds/{fund_id}/position_balances': 1,
                        'funds/{fund_id}/positions': 1,
                        'funds/{fund_id}/positions/{id}': 1,
                        'transactions': 1,
                        'transactions/{id}': 1,
                        'withdraw_limits/{id}': 1,
                        'withdraw_limits': 1,
                    },
                    'post': {
                        'atms/withdraw': 1,
                        'funds/{fund_id}/orders': 1,
                    },
                    'delete': {
                        'funds/{fund_id}/orders/{id}': 1,
                        'funds/{fund_id}/orders/remove_all': 1,
                    },
                },
            },
            'fees': {
                'trading': {
                    'maker': self.parse_number('0.002'),
                    'taker': self.parse_number('0.002'),
                },
                'funding': {
                    'tierBased': False,
                    'percentage': False,
                    'withdraw': {},
                    'deposit': {
                        'BTC': 0,
                        'BCH': 0,
                        'PPC': 0,
                        'ETH': 0,
                        'ZEC': 0,
                        'LTC': 0,
                        'EUR': 0,
                    },
                },
            },
            'exceptions': {
                'exact': {
                    'Request already running': BadRequest,
                    'cannot specify multiple address types': BadRequest,
                    'Currency is not included in the list': BadRequest,
                    'Record not found': OrderNotFound,
                },
                'broad': {
                    'before must be greater than after param': BadRequest,
                    'must be shorter than 60 days': BadRequest,
                    'must be a multiple of(period param) in minutes': BadRequest,
                    'Address allocation limit reached for currency': InvalidAddress,
                    'is not a valid value for param currency': BadRequest,
                    ' is invalid': InvalidAddress,
                },
            },
            'options': {
                'withdraw': {
                    'fillResponseFromRequest': True,
                },
            },
        })

    def fetch_markets(self, params={}):
        """
        retrieves data on all markets for therock
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [dict]: an array of objects representing market data
        """
        response = self.publicGetFunds(params)
        #
        #    {
        #        funds: [
        #            {
        #                id: "BTCEUR",
        #                description: "Trade Bitcoin with Euro",
        #                type: "currency",
        #                base_currency: "EUR",
        #                trade_currency: "BTC",
        #                buy_fee: 0.2,
        #                sell_fee: 0.2,
        #                minimum_price_offer: 0.01,
        #                minimum_quantity_offer: 0.0005,
        #                base_currency_decimals: 2,
        #                trade_currency_decimals: 4,
        #                leverages: []
        #            },
        #            ...
        #        ]
        #    }
        #
        markets = self.safe_value(response, 'funds')
        result = []
        if markets is None:
            raise ExchangeError(self.id + ' fetchMarkets() got an unexpected response')
        else:
            for i in range(0, len(markets)):
                market = markets[i]
                id = self.safe_string(market, 'id')
                baseId = self.safe_string(market, 'trade_currency')
                quoteId = self.safe_string(market, 'base_currency')
                base = self.safe_currency_code(baseId)
                quote = self.safe_currency_code(quoteId)
                buy_fee = self.safe_string(market, 'buy_fee')
                sell_fee = self.safe_string(market, 'sell_fee')
                taker = Precise.string_max(buy_fee, sell_fee)
                taker = self.parse_number(Precise.string_div(taker, '100'))
                leverages = self.safe_value(market, 'leverages')
                leveragesLength = len(leverages)
                result.append({
                    'id': id,
                    'symbol': base + '/' + quote,
                    'base': base,
                    'quote': quote,
                    'settle': None,
                    'baseId': baseId,
                    'quoteId': quoteId,
                    'settleId': None,
                    'type': 'spot',
                    'spot': True,
                    'margin': leveragesLength > 0,
                    'swap': False,
                    'future': False,
                    'option': False,
                    'contract': False,
                    'linear': None,
                    'inverse': None,
                    'taker': taker,
                    'maker': taker,
                    'contractSize': None,
                    'active': True,
                    'expiry': None,
                    'expiryDatetime': None,
                    'strike': None,
                    'optionType': None,
                    'precision': {
                        'amount': self.safe_integer(market, 'trade_currency_decimals'),
                        'price': self.safe_integer(market, 'base_currency_decimals'),
                    },
                    'limits': {
                        'leverage': {
                            'min': 1,
                            'max': self.safe_value(leverages, leveragesLength - 1, 1),
                        },
                        'amount': {
                            'min': self.safe_number(market, 'minimum_quantity_offer'),
                            'max': None,
                        },
                        'price': {
                            'min': self.safe_number(market, 'minimum_price_offer'),
                            'max': None,
                        },
                        'cost': {
                            'min': None,
                            'max': None,
                        },
                    },
                    'info': market,
                })
        return result

    def parse_balance(self, response):
        balances = self.safe_value(response, 'balances', [])
        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['free'] = self.safe_string(balance, 'trading_balance')
            account['total'] = self.safe_string(balance, 'balance')
            result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict params: extra parameters specific to the therock api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        self.load_markets()
        response = self.privateGetBalances(params)
        return self.parse_balance(response)

    def fetch_order_book(self, symbol, limit=None, params={}):
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the therock api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        request = {
            'id': self.market_id(symbol),
        }
        orderbook = self.publicGetFundsIdOrderbook(self.extend(request, params))
        timestamp = self.parse8601(self.safe_string(orderbook, 'date'))
        return self.parse_order_book(orderbook, symbol, timestamp, 'bids', 'asks', 'price', 'amount')

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         "date":"2022-01-16T00:05:08.192Z",
        #         "fund_id":"ETHBTC",
        #         "bid":0.07707802,
        #         "ask":0.07733404,
        #         "last":0.07739053,
        #         "open":0.07628192,
        #         "close":0.07687651,
        #         "low":0.07612047,
        #         "high":0.07703306,
        #         "volume":1.10179665,
        #         "volume_traded":14.273
        #     }
        #
        timestamp = self.parse8601(self.safe_string(ticker, 'date'))
        market = self.safe_market(None, market)
        last = self.safe_string(ticker, 'last')
        return self.safe_ticker({
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': self.safe_string(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'ask'),
            'askVolume': None,
            'vwap': None,
            'open': self.safe_string(ticker, 'open'),
            'close': last,
            'last': last,
            'previousClose': self.safe_string(ticker, 'close'),  # previous day close, if any
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_string(ticker, 'volume_traded'),
            'quoteVolume': self.safe_string(ticker, 'volume'),
            'info': ticker,
        }, market)

    def fetch_tickers(self, symbols=None, params={}):
        """
        fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
        :param [str]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict params: extra parameters specific to the therock api endpoint
        :returns dict: an array of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        response = self.publicGetFundsTickers(params)
        tickers = self.index_by(response['tickers'], 'fund_id')
        ids = list(tickers.keys())
        result = {}
        for i in range(0, len(ids)):
            id = ids[i]
            market = self.safe_market(id)
            symbol = market['symbol']
            ticker = tickers[id]
            result[symbol] = self.parse_ticker(ticker, market)
        return self.filter_by_array(result, 'symbol', symbols)

    def fetch_ticker(self, symbol, params={}):
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the therock api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'id': market['id'],
        }
        response = self.publicGetFundsIdTicker(self.extend(request, params))
        #
        #     {
        #         "date":"2022-01-16T00:05:08.192Z",
        #         "fund_id":"ETHBTC",
        #         "bid":0.07707802,
        #         "ask":0.07733404,
        #         "last":0.07739053,
        #         "open":0.07628192,
        #         "close":0.07687651,
        #         "low":0.07612047,
        #         "high":0.07703306,
        #         "volume":1.10179665,
        #         "volume_traded":14.273
        #     }
        #
        return self.parse_ticker(response, market)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades, fetchOrder trades
        #
        #     {     id:  4493548,
        #       fund_id: "ETHBTC",
        #        amount:  0.203,
        #         price:  0.02783576,
        #          side: "buy",
        #          dark:  False,
        #          date: "2018-11-30T08:19:18.236Z"}
        #
        # fetchMyTrades
        #
        #     {          id:    237338,
        #            fund_id:   "BTCEUR",
        #             amount:    0.348,
        #              price:    348,
        #               side:   "sell",
        #               dark:    False,
        #           order_id:    14920648,
        #               date:   "2015-06-03T00:49:49.000Z",
        #       transactions: [{      id:  2770768,
        #                             date: "2015-06-03T00:49:49.000Z",
        #                             type: "sold_currency_to_fund",
        #                            price:  121.1,
        #                         currency: "EUR"                       },
        #                       {      id:  2770769,
        #                             date: "2015-06-03T00:49:49.000Z",
        #                             type: "released_currency_to_fund",
        #                            price:  0.348,
        #                         currency: "BTC"                        },
        #                       {      id:  2770772,
        #                             date: "2015-06-03T00:49:49.000Z",
        #                             type: "paid_commission",
        #                            price:  0.06,
        #                         currency: "EUR",
        #                         trade_id:  440492                     }   ]}
        #
        marketId = self.safe_string(trade, 'fund_id')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.parse8601(self.safe_string(trade, 'date'))
        id = self.safe_string(trade, 'id')
        orderId = self.safe_string(trade, 'order_id')
        side = self.safe_string(trade, 'side')
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'amount')
        fee = None
        feeCostString = None
        transactions = self.safe_value(trade, 'transactions', [])
        transactionsByType = self.group_by(transactions, 'type')
        feeTransactions = self.safe_value(transactionsByType, 'paid_commission', [])
        for i in range(0, len(feeTransactions)):
            if feeCostString is None:
                feeCostString = '0.0'
            feeCostString = Precise.string_add(feeCostString, self.safe_string(feeTransactions[i], 'price'))
        if feeCostString is not None:
            fee = {
                'cost': feeCostString,
                'currency': market['quote'],
            }
        return self.safe_trade({
            'info': trade,
            'id': id,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': None,
            'side': side,
            'takerOrMaker': None,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': fee,
        }, market)

    def parse_ledger_entry_direction(self, direction):
        directions = {
            'affiliate_earnings': 'in',
            'atm_payment': 'in',
            'bought_currency_from_fund': 'out',
            'bought_shares': 'out',
            'paid_commission': 'out',
            'paypal_payment': 'in',
            'pos_payment': 'in',
            'released_currency_to_fund': 'out',
            'rollover_commission': 'out',
            'sold_currency_to_fund': 'in',
            'sold_shares': 'in',
            'transfer_received': 'in',
            'transfer_sent': 'out',
            'withdraw': 'out',
            # commented types will be shown as-is
            # 'acquired_currency_from_fund': '',
            # 'acquired_insurance': '',
            # 'dividend_distributed_to_holders': '',
            # 'dividend_from_shares': '',
            # 'exposed_position': '',
            # 'insurances_reimbursement': '',
            # 'lent_currency': '',
            # 'linden_lab_assessment': '',
            # 'position_transfer_received': '',
            # 'return_lent_currency': '',
            # 'returned_lent_currency': '',
            # 'the_rock_assessment': '',
        }
        return self.safe_string(directions, direction, direction)

    def parse_ledger_entry_type(self, type):
        types = {
            'affiliate_earnings': 'referral',
            'atm_payment': 'transaction',
            'bought_currency_from_fund': 'trade',
            'bought_shares': 'trade',
            'paid_commission': 'fee',
            'paypal_payment': 'transaction',
            'pos_payment': 'transaction',
            'released_currency_to_fund': 'trade',
            'rollover_commission': 'fee',
            'sold_currency_to_fund': 'trade',
            'sold_shares': 'trade',
            'transfer_received': 'transfer',
            'transfer_sent': 'transfer',
            'withdraw': 'transaction',
            # commented types will be shown as-is
            # 'acquired_currency_from_fund': '',
            # 'acquired_insurance': '',
            # 'dividend_distributed_to_holders': '',
            # 'dividend_from_shares': '',
            # 'exposed_position': '',
            # 'insurances_reimbursement': '',
            # 'lent_currency': '',
            # 'linden_lab_assessment': '',
            # 'position_transfer_received': '',
            # 'return_lent_currency': '',
            # 'returned_lent_currency': '',
            # 'the_rock_assessment': '',
        }
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item, currency=None):
        #
        # withdrawal
        #
        #     {
        #         "id": 21311223,
        #         "date": "2015-06-30T13:55:11.000Z",
        #         "type": "withdraw",
        #         "price": 103.00,
        #         "currency": "EUR",
        #         "fund_id": null,
        #         "order_id": null,
        #         "trade_id": null,
        #         "transfer_detail": {
        #             "method": "wire_transfer",
        #             "id": "F112DD3",
        #             "recipient": "IT123456789012",
        #             "confirmations": 0
        #         }
        #     }
        #
        # deposit
        #
        #     {
        #         "id": 21311222,
        #         "date": "2015-06-30T13:55:11.000Z",
        #         "type": "atm_payment",
        #         "price": 2.01291,
        #         "currency": "BTC",
        #         "fund_id": "null",
        #         "order_id": null,
        #         "trade_id": null,
        #         "transfer_detail": {
        #             "method": "bitcoin",
        #             "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
        #             "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf",
        #             "confirmations": 3
        #         }
        #     }
        #
        # trade fee
        #
        #     {
        #         "id": 21311221,
        #         "date": "2015-06-30T13:55:11.000Z",
        #         "type": "paid_commission",
        #         "price": 0.0001,
        #         "fund_id": "BTCEUR",
        #         "order_id": 12832371,
        #         "trade_id": 12923212,
        #         "currency": "BTC",
        #         "transfer_detail": null
        #     }
        #
        id = self.safe_string(item, 'id')
        referenceId = None
        type = self.safe_string(item, 'type')
        direction = self.parse_ledger_entry_direction(type)
        type = self.parse_ledger_entry_type(type)
        if type == 'trade' or type == 'fee':
            referenceId = self.safe_string(item, 'trade_id')
        currencyId = self.safe_string(item, 'currency')
        code = self.safe_currency_code(currencyId)
        amount = self.safe_number(item, 'price')
        timestamp = self.parse8601(self.safe_string(item, 'date'))
        status = 'ok'
        return {
            'info': item,
            'id': id,
            'direction': direction,
            'account': None,
            'referenceId': referenceId,
            'referenceAccount': None,
            'type': type,
            'currency': code,
            'amount': amount,
            'before': None,
            'after': None,
            'status': status,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': None,
        }

    def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'page': 1,
            # 'fund_id': 'ETHBTC',  # filter by fund symbol
            # 'currency': 'BTC',  # filter by currency
            # 'after': '2015-02-06T08:47:26Z',  # filter after a certain timestamp
            # 'before': '2015-02-06T08:47:26Z',
            # 'type': 'withdraw',
            # 'order_id': '12832371',  # filter by a specific order ID
            # 'trade_id': '12923212',  # filter by a specific trade ID
            # 'transfer_method': 'bitcoin',  # wire_transfer, ripple, greenaddress, bitcoin, litecoin, namecoin, peercoin, dogecoin
            # 'transfer_recipient': '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm',  # filter by a specific recipient(e.g. Bitcoin address, IBAN)
            # 'transfer_id': '8261949194985b01985006724dca5d6059989e096fa95608271d00dd902327fa',  # filter by a specific transfer ID(e.g. Bitcoin TX hash)
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if since is not None:
            request['after'] = self.iso8601(since)
        response = self.privateGetTransactions(self.extend(request, params))
        #
        #     {
        #         "transactions": [
        #             {
        #                 "id": 21311223,
        #                 "date": "2015-06-30T13:55:11.000Z",
        #                 "type": "withdraw",
        #                 "price": 103.00,
        #                 "currency": "EUR",
        #                 "fund_id": null,
        #                 "order_id": null,
        #                 "trade_id": null,
        #                 "transfer_detail": {
        #                     "method": "wire_transfer",
        #                     "id": "F112DD3",
        #                     "recipient": "IT123456789012",
        #                     "confirmations": 0
        #                 }
        #             },
        #             {
        #                 "id": 21311222,
        #                 "date": "2015-06-30T13:55:11.000Z",
        #                 "type": "atm_payment",
        #                 "price": 2.01291,
        #                 "currency": "BTC",
        #                 "fund_id": "null",
        #                 "order_id": null,
        #                 "trade_id": null,
        #                 "transfer_detail": {
        #                     "method": "bitcoin",
        #                     "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
        #                     "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf",
        #                     "confirmations": 3
        #                 }
        #             },
        #             {
        #                 "id": 21311221,
        #                 "date": "2015-06-30T13:55:11.000Z",
        #                 "type": "paid_commission",
        #                 "price": 0.0001,
        #                 "fund_id": "BTCEUR",
        #                 "order_id": 12832371,
        #                 "trade_id": 12923212,
        #                 "currency": "BTC",
        #                 "transfer_detail": null
        #             }
        #         ],
        #         "meta": {
        #             "total_count": 1221,
        #             "first": {"page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1"},
        #             "previous": null,
        #             "current": {"page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1"},
        #             "next": {"page": 2, "href": "https://api.therocktrading.com/v1/transactions?page=2"},
        #             "last": {"page": 1221, "href": "https://api.therocktrading.com/v1/transactions?page=1221"}
        #         }
        #     }
        #
        transactions = self.safe_value(response, 'transactions', [])
        return self.parse_ledger(transactions, currency, since, limit)

    def parse_transaction_type(self, type):
        types = {
            'withdraw': 'withdrawal',
            'atm_payment': 'deposit',
        }
        return self.safe_string(types, type, type)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchWithdrawals
        #
        #     # fiat
        #
        #     {
        #         "id": 21311223,
        #         "date": "2015-06-30T13:55:11.000Z",
        #         "type": "withdraw",
        #         "price": 103.00,
        #         "currency": "EUR",
        #         "fund_id": null,
        #         "order_id": null,
        #         "trade_id": null,
        #         "transfer_detail": {
        #             "method": "wire_transfer",
        #             "id": "F112DD3",
        #             "recipient": "IT123456789012",
        #             "confirmations": 0
        #         }
        #     }
        #
        #     {
        #         "id": 12564223,
        #         "date": "2017-08-07T08:13:50.023Z",
        #         "note": "GB7IDL401573388",
        #         "type": "withdraw",
        #         "price": 4345.93,
        #         "fund_id": null,
        #         "currency": "EUR",
        #         "order_id": null,
        #         "trade_id": null,
        #         "transfer_detail": {
        #             "id": "EXECUTEDBUTUNCHECKED",
        #             "method": "wire_transfer",
        #             "recipient": "GB7IDL401573388",
        #             "confirmations": 0
        #         }
        #     }
        #
        #     # crypto
        #
        #     {
        #         id: 20914695,
        #         date: '2018-02-24T07:13:23.002Z',
        #         type: 'withdraw',
        #         price: 2.70883607,
        #         currency: 'BCH',
        #         fund_id: null,
        #         order_id: null,
        #         trade_id: null,
        #         note: '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm',
        #         transfer_detail: {
        #             method: 'bitcoin_cash',
        #             id: '8261949194985b01985006724dca5d6059989e096fa95608271d00dd902327fa',
        #             recipient: '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm',
        #             confirmations: 0
        #         }
        #     }
        #
        #
        # fetchDeposits
        #
        #     # fiat
        #
        #     {
        #         id: 16176632,
        #         date: '2017-11-20T21:00:13.355Z',
        #         type: 'atm_payment',
        #         price: 5000,
        #         currency: 'EUR',
        #         fund_id: null,
        #         order_id: null,
        #         trade_id: null,
        #         note: 'Mistral deposit',
        #         transfer_detail: {
        #             method: 'wire_transfer',
        #             id: '972JQ49337DX769T',
        #             recipient: null,
        #             confirmations: 0
        #         }
        #     }
        #
        #     # crypto
        #
        #     {
        #         "id": 21311222,
        #         "date": "2015-06-30T13:55:11.000Z",
        #         "type": "atm_payment",
        #         "price": 2.01291,
        #         "currency": "BTC",
        #         "fund_id": "null",
        #         "order_id": null,
        #         "trade_id": null,
        #         "transfer_detail": {
        #             "method": "bitcoin",
        #             "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
        #             "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf",
        #             "confirmations": 3
        #         }
        #     }
        #
        # privatePostAtmsWithdraw
        #    {"transaction_id": 65088485}
        #
        id = self.safe_string_2(transaction, 'id', 'transaction_id')
        type = self.parse_transaction_type(self.safe_string(transaction, 'type'))
        detail = self.safe_value(transaction, 'transfer_detail', {})
        method = self.safe_string(detail, 'method')
        txid = None
        address = None
        if method is not None:
            if method != 'wire_transfer':
                txid = self.safe_string(detail, 'id')
                address = self.safe_string(detail, 'recipient')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId)
        amount = self.safe_number(transaction, 'price')
        timestamp = self.parse8601(self.safe_string(transaction, 'date'))
        status = 'ok'
        network = self.safe_string(detail, 'method')
        # todo parse tags
        return {
            'info': transaction,
            'id': id,
            'currency': code,
            'amount': amount,
            'network': network,
            'addressFrom': None,
            'addressTo': address,
            'address': address,
            'tagFrom': None,
            'tagTo': None,
            'tag': None,
            'status': status,
            'type': type,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': None,
        }

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        request = {
            'type': 'withdraw',
        }
        return self.fetch_transactions(code, since, limit, self.extend(request, params))

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        request = {
            'type': 'atm_payment',
        }
        return self.fetch_transactions(code, since, limit, self.extend(request, params))

    def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'page': 1,
            # 'fund_id': 'ETHBTC',  # filter by fund symbol
            # 'currency': 'BTC',  # filter by currency
            # 'after': '2015-02-06T08:47:26Z',  # filter after a certain timestamp
            # 'before': '2015-02-06T08:47:26Z',
            # 'type': 'withdraw',
            # 'order_id': '12832371',  # filter by a specific order ID
            # 'trade_id': '12923212',  # filter by a specific trade ID
            # 'transfer_method': 'bitcoin',  # wire_transfer, ripple, greenaddress, bitcoin, litecoin, namecoin, peercoin, dogecoin
            # 'transfer_recipient': '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm',  # filter by a specific recipient(e.g. Bitcoin address, IBAN)
            # 'transfer_id': '8261949194985b01985006724dca5d6059989e096fa95608271d00dd902327fa',  # filter by a specific transfer ID(e.g. Bitcoin TX hash)
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if since is not None:
            request['after'] = self.iso8601(since)
        params = self.extend(request, params)
        response = self.privateGetTransactions(params)
        #
        #     {
        #         "transactions": [
        #             {
        #                 "id": 21311223,
        #                 "date": "2015-06-30T13:55:11.000Z",
        #                 "type": "withdraw",
        #                 "price": 103.00,
        #                 "currency": "EUR",
        #                 "fund_id": null,
        #                 "order_id": null,
        #                 "trade_id": null,
        #                 "transfer_detail": {
        #                     "method": "wire_transfer",
        #                     "id": "F112DD3",
        #                     "recipient": "IT123456789012",
        #                     "confirmations": 0
        #                 }
        #             },
        #             {
        #                 "id": 21311222,
        #                 "date": "2015-06-30T13:55:11.000Z",
        #                 "type": "atm_payment",
        #                 "price": 2.01291,
        #                 "currency": "BTC",
        #                 "fund_id": "null",
        #                 "order_id": null,
        #                 "trade_id": null,
        #                 "transfer_detail": {
        #                     "method": "bitcoin",
        #                     "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
        #                     "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf",
        #                     "confirmations": 3
        #                 }
        #             },
        #             {
        #                 "id": 21311221,
        #                 "date": "2015-06-30T13:55:11.000Z",
        #                 "type": "paid_commission",
        #                 "price": 0.0001,
        #                 "fund_id": "BTCEUR",
        #                 "order_id": 12832371,
        #                 "trade_id": 12923212,
        #                 "currency": "BTC",
        #                 "transfer_detail": null
        #             }
        #         ],
        #         "meta": {
        #             "total_count": 1221,
        #             "first": {"page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1"},
        #             "previous": null,
        #             "current": {"page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1"},
        #             "next": {"page": 2, "href": "https://api.therocktrading.com/v1/transactions?page=2"},
        #             "last": {"page": 1221, "href": "https://api.therocktrading.com/v1/transactions?page=1221"}
        #         }
        #     }
        #
        transactions = self.safe_value(response, 'transactions', [])
        transactionTypes = ['withdraw', 'atm_payment']
        depositsAndWithdrawals = self.filter_by_array(transactions, 'type', transactionTypes, False)
        return self.parse_transactions(depositsAndWithdrawals, currency, since, limit)

    def withdraw(self, code, amount, address, tag=None, params={}):
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.load_markets()
        currency = self.currency(code)
        amount = self.currency_to_precision(code, amount)
        request = {
            'currency': currency['id'],
            'destination_address': address,
            'amount': float(amount),
        }
        if tag is not None:
            request['destination_tag'] = tag
        # requires write permission on the wallet
        response = self.privatePostAtmsWithdraw(self.extend(request, params))
        #
        #    {"transaction_id": 65088485}
        #
        transaction = self.parse_transaction(response, currency)
        withdrawOptions = self.safe_value(self.options, 'withdraw', {})
        fillResponseFromRequest = self.safe_value(withdrawOptions, 'fillResponseFromRequest', True)
        if fillResponseFromRequest:
            if transaction['addressTo'] == address:
                transaction['addressTo'] = address
            if transaction['address'] is None:
                transaction['address'] = address
            if transaction['tagTo'] is None:
                transaction['tagTo'] = tag
            if transaction['tag'] is None:
                transaction['tag'] = tag
            if transaction['amount'] is None:
                transaction['amount'] = amount
        return transaction

    def parse_order_status(self, status):
        statuses = {
            'active': 'open',
            'executed': 'closed',
            'deleted': 'canceled',
            # don't know what self status means
            # 'conditional': '?',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        #     {
        #         "id": 4325578,
        #         "fund_id":"BTCEUR",
        #         "side":"buy",
        #         "type":"limit",
        #         "status":"executed",
        #         "price":0.0102,
        #         "amount": 50.0,
        #         "amount_unfilled": 0.0,
        #         "conditional_type": null,
        #         "conditional_price": null,
        #         "date":"2015-06-03T00:49:48.000Z",
        #         "close_on": nil,
        #         "leverage": 1.0,
        #         "position_id": null,
        #         "trades": [
        #             {
        #                 "id":237338,
        #                 "fund_id":"BTCEUR",
        #                 "amount":50,
        #                 "price":0.0102,
        #                 "side":"buy",
        #                 "dark":false,
        #                 "date":"2015-06-03T00:49:49.000Z"
        #             }
        #         ]
        #     }
        #
        id = self.safe_string(order, 'id')
        marketId = self.safe_string(order, 'fund_id')
        symbol = self.safe_symbol(marketId, market)
        status = self.parse_order_status(self.safe_string(order, 'status'))
        timestamp = self.parse8601(self.safe_string(order, 'date'))
        type = self.safe_string(order, 'type')
        side = self.safe_string(order, 'side')
        amount = self.safe_number(order, 'amount')
        remaining = self.safe_number(order, 'amount_unfilled')
        filled = None
        if amount is not None:
            if remaining is not None:
                filled = amount - remaining
        price = self.safe_number(order, 'price')
        trades = self.safe_value(order, 'trades')
        cost = None
        average = None
        lastTradeTimestamp = None
        if trades is not None:
            numTrades = len(trades)
            if numTrades > 0:
                trades = self.parse_trades(trades, market, None, None, {
                    'orderId': id,
                })
                # todo: determine the cost and the average price from trades
                cost = 0
                filled = 0
                for i in range(0, numTrades):
                    trade = trades[i]
                    cost = self.sum(cost, trade['cost'])
                    filled = self.sum(filled, trade['amount'])
                if filled > 0:
                    average = cost / filled
                lastTradeTimestamp = trades[numTrades - 1]['timestamp']
            else:
                cost = 0
        stopPrice = self.safe_number(order, 'conditional_price')
        return {
            'id': id,
            'clientOrderId': None,
            'info': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'average': average,
            'remaining': remaining,
            'fee': None,
            'trades': trades,
        }

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'status': 'active',
        }
        return self.fetch_orders(symbol, since, limit, self.extend(request, params))

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'status': 'executed',
        }
        return self.fetch_orders(symbol, since, limit, self.extend(request, params))

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'fund_id': market['id'],
            # 'after': '2015-02-06T08:47:26Z',
            # 'before': '2015-02-06T08:47:26Z'
            # 'status': 'active',  # 'executed', 'conditional'
            # 'side': 'buy',  # 'sell'
            # 'position_id': 123,  # filter orders by margin position id
        }
        if since is not None:
            request['after'] = self.iso8601(since)
        response = self.privateGetFundsFundIdOrders(self.extend(request, params))
        #
        #     {
        #         orders: [
        #             {
        #                 id: 299333648,
        #                 fund_id: 'BTCEUR',
        #                 side: 'sell',
        #                 type: 'limit',
        #                 status: 'executed',
        #                 price: 5821,
        #                 amount: 0.1,
        #                 amount_unfilled: 0,
        #                 conditional_type: null,
        #                 conditional_price: null,
        #                 date: '2018-06-18T17:38:16.129Z',
        #                 close_on: null,
        #                 dark: False,
        #                 leverage: 1,
        #                 position_id: 0
        #             }
        #         ]
        #     }
        #
        orders = self.safe_value(response, 'orders', [])
        return self.parse_orders(orders, market, since, limit)

    def fetch_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'id': id,
            'fund_id': market['id'],
        }
        response = self.privateGetFundsFundIdOrdersId(self.extend(request, params))
        #
        #     {
        #         "id": 4325578,
        #         "fund_id":"BTCEUR",
        #         "side":"buy",
        #         "type":"limit",
        #         "status":"executed",
        #         "price":0.0102,
        #         "amount": 50.0,
        #         "amount_unfilled": 0.0,
        #         "conditional_type": null,
        #         "conditional_price": null,
        #         "date":"2015-06-03T00:49:48.000Z",
        #         "close_on": null,
        #         "leverage": 1.0,
        #         "position_id": null,
        #         "trades": [
        #             {
        #                 "id":237338,
        #                 "fund_id":"BTCEUR",
        #                 "amount":50,
        #                 "price":0.0102,
        #                 "side":"buy",
        #                 "dark":false,
        #                 "date":"2015-06-03T00:49:49.000Z"
        #             }
        #         ]
        #     }
        #
        return self.parse_order(response)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        """
        create a trade order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float price: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict params: extra parameters specific to the therock api endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        if type == 'market':
            price = 0
        request = {
            'fund_id': self.market_id(symbol),
            'side': side,
            'amount': amount,
            'price': price,
        }
        response = self.privatePostFundsFundIdOrders(self.extend(request, params))
        return self.parse_order(response)

    def cancel_order(self, id, symbol=None, params={}):
        self.load_markets()
        request = {
            'id': id,
            'fund_id': self.market_id(symbol),
        }
        response = self.privateDeleteFundsFundIdOrdersId(self.extend(request, params))
        return self.parse_order(response)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'id': market['id'],
        }
        if limit is not None:
            request['per_page'] = limit  # default 25 max 200
        if since is not None:
            request['after'] = self.iso8601(since)
        response = self.privateGetFundsIdTrades(self.extend(request, params))
        #
        #     {
        #         "trades": [
        #             {
        #                 "id":237338,
        #                 "fund_id":"BTCEUR",
        #                 "amount":0.348,
        #                 "price":348.0,
        #                 "side":"sell",
        #                 "dark": False,
        #                 "order_id":14920648,
        #                 "date":"2015-06-03T00:49:49.000Z",
        #                 "transactions": [
        #                     {"id": 2770768, "date": "2015-06-03T00:49:49.000Z", "type": "sold_currency_to_fund", "price": 121.1, "currency": "EUR"},
        #                     {"id": 2770769, "date": "2015-06-03T00:49:49.000Z", "type": "released_currency_to_fund", "price": 0.348, "currency": "BTC"},
        #                     {"id": 2770772, "date": "2015-06-03T00:49:49.000Z", "type": "paid_commission", "price": 0.06, "currency": "EUR", "trade_id": 440492},
        #                 ]
        #             }
        #         ],
        #         "meta": {
        #             "total_count": 31,
        #             "first": {"href": "https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=1"},
        #             "previous": null,
        #             "current": {"href": "https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=1"},
        #             "next": {"href": "https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=2"},
        #             "last":{"href":"https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=2"}
        #         }
        #     }
        #
        trades = self.safe_value(response, 'trades', [])
        return self.parse_trades(trades, market, since, limit)

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        """
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the therock api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'id': market['id'],
        }
        if limit is not None:
            request['per_page'] = limit  # default 25 max 200
        if since is not None:
            request['after'] = self.iso8601(since)
        response = self.publicGetFundsIdTrades(self.extend(request, params))
        #
        #     {
        #         trades: [
        #             {
        #                 id:  4493548,
        #                 fund_id: "ETHBTC",
        #                 amount:  0.203,
        #                 price:  0.02783576,
        #                 side: "buy",
        #                 dark:  False,
        #                 date: "2018-11-30T08:19:18.236Z"
        #             },
        #             {
        #                 id:  4492926,
        #                 fund_id: "ETHBTC",
        #                 amount:  0.04,
        #                 price:  0.02767034,
        #                 side: "buy",
        #                 dark:  False,
        #                 date: "2018-11-30T07:03:03.897Z"
        #             }
        #         ],
        #         meta: {
        #             total_count: null,
        #             first: {page: 1, href: "https://api.therocktrading.com/v1/funds/ETHBTC/trades?page=1"},
        #             previous: null,
        #             current: {page:  1, href: "https://api.therocktrading.com/v1/funds/ETHBTC/trades?page=1"},
        #             next: {page:  2, href: "https://api.therocktrading.com/v1/funds/ETHBTC/trades?page=2"},
        #             last: null
        #         }
        #     }
        #
        return self.parse_trades(response['trades'], market, since, limit)

    def fetch_trading_fee(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'id': market['id'],
        }
        response = self.publicGetFundsId(self.extend(request, params))
        #
        #     {
        #         id: 'ETHBTC',
        #         description: 'Trade Ether with Bitcoin',
        #         type: 'currency',
        #         base_currency: 'BTC',
        #         trade_currency: 'ETH',
        #         buy_fee: '0.2',
        #         sell_fee: '0.2',
        #         minimum_price_offer: '0.00000001',
        #         minimum_quantity_offer: '0.005',
        #         base_currency_decimals: '8',
        #         trade_currency_decimals: '3',
        #         leverages: []
        #     }
        #
        request = {
            'id': market['quoteId'],
        }
        discount = self.privateGetDiscountsId(self.extend(request, params))
        #
        #     {
        #         "currency":"BTC",
        #         "discount":50.0,
        #         "details": {
        #             "personal_discount": 50.0,
        #             "commissions_related_discount": 0.0
        #         }
        #     }
        #
        return self.parse_trading_fee(response, discount, market)

    def fetch_trading_fees(self, params={}):
        self.load_markets()
        response = self.publicGetFunds(params)
        #
        #     {
        #         funds: [
        #             {
        #                 id: 'BTCEUR',
        #                 description: 'Trade Bitcoin with Euro',
        #                 type: 'currency',
        #                 base_currency: 'EUR',
        #                 trade_currency: 'BTC',
        #                 buy_fee: '0.2',
        #                 sell_fee: '0.2',
        #                 minimum_price_offer: '0.01',
        #                 minimum_quantity_offer: '0.0005',
        #                 base_currency_decimals: '2',
        #                 trade_currency_decimals: '4',
        #                 leverages: []
        #             },
        #         ]
        #     }
        #
        discountsResponse = self.privateGetDiscounts(params)
        #
        #     {
        #         "discounts": [
        #             {
        #                 "currency":"BTC",
        #                 "discount":50.0,
        #                 "details": {
        #                     "personal_discount": 50.0,
        #                     "commissions_related_discount": 0.0
        #                 }
        #             }
        #         ]
        #     }
        #
        funds = self.safe_value(response, 'funds', [])
        discounts = self.safe_value(discountsResponse, 'discounts', [])
        result = {}
        for i in range(0, len(funds)):
            fund = funds[i]
            marketId = self.safe_string(fund, 'id')
            market = self.safe_market(marketId)
            quoteId = self.safe_value(market, 'quoteId')
            discount = self.filter_by(discounts, 'currency', quoteId)
            fee = self.parse_trading_fee(fund, discount, market)
            symbol = fee['symbol']
            result[symbol] = fee
        return result

    def parse_trading_fee(self, fee, discount=None, market=None):
        marketId = self.safe_string(fee, 'id')
        takerString = self.safe_string(fee, 'buy_fee')
        makerString = self.safe_string(fee, 'sell_fee')
        # TotalFee = (100 - discount) * fee / 10000
        discountString = self.safe_string(discount, 'discount', '0')
        feePercentage = Precise.string_sub('100', discountString)
        taker = self.parse_number(Precise.string_div(Precise.string_mul(takerString, feePercentage), '10000'))
        maker = self.parse_number(Precise.string_div(Precise.string_mul(makerString, feePercentage), '10000'))
        return {
            'info': fee,
            'symbol': self.safe_symbol(marketId, market),
            'maker': maker,
            'taker': taker,
            'percentage': True,
            'tierBased': True,
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'] + '/' + self.version + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        headers = {} if (headers is None) else headers
        if api == 'private':
            self.check_required_credentials()
            if query:
                if method == 'POST':
                    body = self.json(query)
                    headers['Content-Type'] = 'application/json'
                else:
                    queryString = self.rawencode(query)
                    if len(queryString):
                        url += '?' + queryString
            nonce = str(self.nonce())
            auth = nonce + url
            headers['X-TRT-KEY'] = self.apiKey
            headers['X-TRT-NONCE'] = nonce
            headers['X-TRT-SIGN'] = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha512)
        elif api == 'public':
            if query:
                url += '?' + self.rawencode(query)
        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
        #
        #     {
        #         "errors": [
        #             {"message": ":currency is not a valid value for param currency","code": "11","meta": {"key":"currency","value":":currency"}},
        #             {"message": "Address allocation limit reached for currency :currency.","code": "13"},
        #             {"message": "Request already running", "code": "50"},
        #             {"message": "cannot specify multiple address types", "code": "12"},
        #             {"message": ":address_type is invalid", "code": "12"}
        #         ]
        #     }
        #
        errors = self.safe_value(response, 'errors', [])
        numErrors = len(errors)
        if numErrors > 0:
            feedback = self.id + ' ' + body
            # here we raise the first error we can identify
            for i in range(0, numErrors):
                error = errors[i]
                message = self.safe_string(error, 'message')
                self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
                self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            raise ExchangeError(feedback)  # unknown message
