# -*- 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

import ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCacheBySymbolById, ArrayCacheByTimestamp
from ccxt.base.types import Any, Balances, Bool, Int, Order, OrderBook, Str, Strings, Ticker, Tickers, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import NotSupported
from ccxt.base.precise import Precise


class onetrading(ccxt.async_support.onetrading):

    def describe(self) -> Any:
        return self.deep_extend(super(onetrading, self).describe(), {
            'has': {
                'ws': True,
                'watchBalance': True,
                'watchTicker': True,
                'watchTickers': True,
                'watchTrades': False,
                'watchTradesForSymbols': False,
                'watchMyTrades': True,
                'watchOrders': True,
                'watchOrderBook': True,
                'watchOHLCV': True,
            },
            'urls': {
                'api': {
                    'ws': 'wss://streams.onetrading.com/',
                },
            },
            'options': {
                'bp_remaining_quota': 200,
                'timeframes': {
                    '1m': {
                        'unit': 'MINUTES',
                        'period': 1,
                    },
                    '5m': {
                        'unit': 'MINUTES',
                        'period': 5,
                    },
                    '15m': {
                        'unit': 'MINUTES',
                        'period': 15,
                    },
                    '30m': {
                        'unit': 'MINUTES',
                        'period': 30,
                    },
                    '1h': {
                        'unit': 'HOURS',
                        'period': 1,
                    },
                    '4h': {
                        'unit': 'HOURS',
                        'period': 4,
                    },
                    '1d': {
                        'unit': 'DAYS',
                        'period': 1,
                    },
                    '1w': {
                        'unit': 'WEEKS',
                        'period': 1,
                    },
                    '1M': {
                        'unit': 'MONTHS',
                        'period': 1,
                    },
                },
            },
            'streaming': {
            },
            'exceptions': {
            },
        })

    async def watch_balance(self, params={}) -> Balances:
        """

        https://developers.bitpanda.com/exchange/#account-history-channel

        watch balance and get the amount of funds available for trading or funds locked in orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        await self.authenticate(params)
        url = self.urls['api']['ws']
        messageHash = 'balance'
        subscribeHash = 'ACCOUNT_HISTORY'
        bpRemainingQuota = self.safe_integer(self.options, 'bp_remaining_quota', 200)
        subscribe: dict = {
            'type': 'SUBSCRIBE',
            'bp_remaining_quota': bpRemainingQuota,
            'channels': [
                {
                    'name': 'ACCOUNT_HISTORY',
                },
            ],
        }
        request = self.deep_extend(subscribe, params)
        return await self.watch(url, messageHash, request, subscribeHash, request)

    def handle_balance_snapshot(self, client, message):
        #
        # snapshot
        #     {
        #         "account_id": "b355abb8-aaae-4fae-903c-c60ff74723c6",
        #         "type": "BALANCES_SNAPSHOT",
        #         "channel_name": "ACCOUNT_HISTORY",
        #         "time": "2019-04-01T13:39:17.155Z",
        #         "balances": [{
        #                 "account_id": "b355abb8-aaae-4fae-903c-c60ff74723c6",
        #                 "currency_code": "BTC",
        #                 "change": "0.5",
        #                 "available": "10.0",
        #                 "locked": "1.1234567",
        #                 "sequence": 1,
        #                 "time": "2019-04-01T13:39:17.155Z"
        #             },
        #             {
        #                 "account_id": "b355abb8-aaae-4fae-903c-c60ff74723c6",
        #                 "currency_code": "ETH",
        #                 "change": "0.5",
        #                 "available": "10.0",
        #                 "locked": "1.1234567",
        #                 "sequence": 2,
        #                 "time": "2019-04-01T13:39:17.155Z"
        #             }
        #         ]
        #     }
        #
        self.balance = self.parse_balance(message)
        messageHash = 'balance'
        client.resolve(self.balance, messageHash)

    async def watch_ticker(self, symbol: str, params={}) -> Ticker:
        """

        https://developers.bitpanda.com/exchange/#market-ticker-channel

        watches 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 exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        subscriptionHash = 'MARKET_TICKER'
        messageHash = 'ticker.' + symbol
        request: dict = {
            'type': 'SUBSCRIBE',
            'channels': [
                {
                    'name': 'MARKET_TICKER',
                    'price_points_mode': 'INLINE',
                },
            ],
        }
        return await self.watch_many(messageHash, request, subscriptionHash, [symbol], params)

    async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """

        https://developers.bitpanda.com/exchange/#market-ticker-channel

        watches price tickers, a statistical calculation with the information for all markets or those specified.
        :param str symbols: unified symbols of the markets to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an array of `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        if symbols is None:
            symbols = []
        subscriptionHash = 'MARKET_TICKER'
        messageHash = 'tickers'
        request: dict = {
            'type': 'SUBSCRIBE',
            'channels': [
                {
                    'name': 'MARKET_TICKER',
                    'price_points_mode': 'INLINE',
                },
            ],
        }
        tickers = await self.watch_many(messageHash, request, subscriptionHash, symbols, params)
        return self.filter_by_array(tickers, 'symbol', symbols)

    def handle_ticker(self, client: Client, message):
        #
        #     {
        #         "ticker_updates": [{
        #             "instrument": "ETH_BTC",
        #             "last_price": "0.053752",
        #             "price_change": "0.000623",
        #             "price_change_percentage": "1.17",
        #             "high": "0.055",
        #             "low": "0.052662",
        #             "volume": "6.3821593247"
        #         }],
        #         "channel_name": "MARKET_TICKER",
        #         "type": "MARKET_TICKER_UPDATES",
        #         "time": "2022-06-23T16:41:00.004162Z"
        #     }
        #
        tickers = self.safe_value(message, 'ticker_updates', [])
        datetime = self.safe_string(message, 'time')
        for i in range(0, len(tickers)):
            ticker = tickers[i]
            marketId = self.safe_string(ticker, 'instrument')
            symbol = self.safe_symbol(marketId)
            self.tickers[symbol] = self.parse_ws_ticker(ticker)
            timestamp = self.parse8601(datetime)
            self.tickers[symbol]['timestamp'] = timestamp
            self.tickers[symbol]['datetime'] = self.iso8601(timestamp)
            client.resolve(self.tickers[symbol], 'ticker.' + symbol)
        client.resolve(self.tickers, 'tickers')

    def parse_ws_ticker(self, ticker, market=None):
        #
        #     {
        #         "instrument": "ETH_BTC",
        #         "last_price": "0.053752",
        #         "price_change": "-0.000623",
        #         "price_change_percentage": "-1.17",
        #         "high": "0.055",
        #         "low": "0.052662",
        #         "volume": "6.3821593247"
        #     }
        #
        marketId = self.safe_string(ticker, 'instrument')
        return self.safe_ticker({
            'symbol': self.safe_symbol(marketId, market),
            'timestamp': None,
            'datetime': None,
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': self.safe_string(ticker, 'last_price'),
            'last': self.safe_string(ticker, 'last_price'),
            'previousClose': None,
            'change': self.safe_string(ticker, 'price_change'),
            'percentage': self.safe_string(ticker, 'price_change_percentage'),
            'average': None,
            'baseVolume': None,
            'quoteVolume': self.safe_number(ticker, 'volume'),
            'info': ticker,
        }, market)

    async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """

        https://developers.bitpanda.com/exchange/#account-history-channel

        get the list of trades associated with the user
        :param str symbol: unified symbol of the market to fetch trades for. Use 'any' to watch all trades
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        await self.load_markets()
        messageHash = 'myTrades'
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash += ':' + symbol
        await self.authenticate(params)
        url = self.urls['api']['ws']
        subscribeHash = 'ACCOUNT_HISTORY'
        bpRemainingQuota = self.safe_integer(self.options, 'bp_remaining_quota', 200)
        subscribe: dict = {
            'type': 'SUBSCRIBE',
            'bp_remaining_quota': bpRemainingQuota,
            'channels': [
                {
                    'name': 'ACCOUNT_HISTORY',
                },
            ],
        }
        request = self.deep_extend(subscribe, params)
        trades = await self.watch(url, messageHash, request, subscribeHash, request)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        trades = self.filter_by_symbol_since_limit(trades, symbol, since, limit)
        numTrades = len(trades)
        if numTrades == 0:
            return await self.watch_my_trades(symbol, since, limit, params)
        return trades

    async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """

        https://developers.bitpanda.com/exchange/#market-ticker-channel

        watches 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 [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        messageHash = 'book:' + symbol
        subscriptionHash = 'ORDER_BOOK'
        depth = 0
        if limit is not None:
            depth = limit
        request: dict = {
            'type': 'SUBSCRIBE',
            'channels': [
                {
                    'name': 'ORDER_BOOK',
                    'depth': depth,
                },
            ],
        }
        orderbook = await self.watch_many(messageHash, request, subscriptionHash, [symbol], params)
        return orderbook.limit()

    def handle_order_book(self, client: Client, message):
        #
        #  snapshot
        #     {
        #         "instrument_code": "ETH_BTC",
        #         "bids": [
        #             ['0.053595', "4.5352"],
        #             ...
        #         ],
        #         "asks": [
        #             ['0.055455', "0.2821"],
        #             ...
        #         ],
        #         "channel_name": "ORDER_BOOK",
        #         "type": "ORDER_BOOK_SNAPSHOT",
        #         "time": "2022-06-23T15:38:02.196282Z"
        #     }
        #
        #  update
        #     {
        #         "instrument_code": "ETH_BTC",
        #         "changes": [
        #             ["BUY", '0.053593', "8.0587"]
        #         ],
        #         "channel_name": "ORDER_BOOK",
        #         "type": "ORDER_BOOK_UPDATE",
        #         "time": "2022-06-23T15:38:02.751301Z"
        #     }
        #
        type = self.safe_string(message, 'type')
        marketId = self.safe_string(message, 'instrument_code')
        symbol = self.safe_symbol(marketId)
        dateTime = self.safe_string(message, 'time')
        timestamp = self.parse8601(dateTime)
        channel = 'book:' + symbol
        orderbook = self.safe_value(self.orderbooks, symbol)
        if orderbook is None:
            orderbook = self.order_book({})
        if type == 'ORDER_BOOK_SNAPSHOT':
            snapshot = self.parse_order_book(message, symbol, timestamp, 'bids', 'asks')
            orderbook.reset(snapshot)
        elif type == 'ORDER_BOOK_UPDATE':
            changes = self.safe_value(message, 'changes', [])
            self.handle_deltas(orderbook, changes)
        else:
            raise NotSupported(self.id + ' watchOrderBook() did not recognize message type ' + type)
        orderbook['nonce'] = timestamp
        orderbook['timestamp'] = timestamp
        orderbook['datetime'] = self.iso8601(timestamp)
        self.orderbooks[symbol] = orderbook
        client.resolve(orderbook, channel)

    def handle_delta(self, orderbook, delta):
        #
        #   ['BUY', "0.053595", "0"]
        #
        bidAsk = self.parse_bid_ask(delta, 1, 2)
        type = self.safe_string(delta, 0)
        if type == 'BUY':
            bids = orderbook['bids']
            bids.storeArray(bidAsk)
        elif type == 'SELL':
            asks = orderbook['asks']
            asks.storeArray(bidAsk)
        else:
            raise NotSupported(self.id + ' watchOrderBook() received unknown change type ' + self.json(delta))

    def handle_deltas(self, orderbook, deltas):
        #
        #    [
        #       ['BUY', "0.053593", "0"],
        #       ['SELL', "0.053698", "0"]
        #    ]
        #
        for i in range(0, len(deltas)):
            self.handle_delta(orderbook, deltas[i])

    async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """

        https://developers.bitpanda.com/exchange/#account-history-channel

        watches information on multiple orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.channel]: can listen to orders using ACCOUNT_HISTORY or TRADING
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        messageHash = 'orders'
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash += ':' + symbol
        await self.authenticate(params)
        url = self.urls['api']['ws']
        subscribeHash = self.safe_string(params, 'channel', 'ACCOUNT_HISTORY')
        bpRemainingQuota = self.safe_integer(self.options, 'bp_remaining_quota', 200)
        subscribe: dict = {
            'type': 'SUBSCRIBE',
            'bp_remaining_quota': bpRemainingQuota,
            'channels': [
                {
                    'name': subscribeHash,
                },
            ],
        }
        request = self.deep_extend(subscribe, params)
        orders = await self.watch(url, messageHash, request, subscribeHash, request)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        orders = self.filter_by_symbol_since_limit(orders, symbol, since, limit)
        numOrders = len(orders)
        if numOrders == 0:
            return await self.watch_orders(symbol, since, limit, params)
        return orders

    def handle_trading(self, client: Client, message):
        #
        #     {
        #         "order_book_sequence": 892925263,
        #         "side": "BUY",
        #         "amount": "0.00046",
        #         "trade_id": "d67b9b69-ab76-480f-9ba3-b33582202836",
        #         "matched_as": "TAKER",
        #         "matched_amount": "0.00046",
        #         "matched_price": "22231.08",
        #         "instrument_code": "BTC_EUR",
        #         "order_id": "7b39f316-0a71-4bfd-adda-3062e6f0bd37",
        #         "remaining": "0.0",
        #         "channel_name": "TRADING",
        #         "type": "FILL",
        #         "time": "2022-07-21T12:41:22.883341Z"
        #     }
        #
        #     {
        #         "status": "CANCELLED",
        #         "order_book_sequence": 892928424,
        #         "amount": "0.0003",
        #         "side": "SELL",
        #         "price": "50338.65",
        #         "instrument_code": "BTC_EUR",
        #         "order_id": "b3994a08-a9e8-4a79-a08b-33e3480382df",
        #         "remaining": "0.0003",
        #         "channel_name": "TRADING",
        #         "type": "DONE",
        #         "time": "2022-07-21T12:44:24.267000Z"
        #     }
        #
        #     {
        #         "order_book_sequence": 892934476,
        #         "side": "SELL",
        #         "amount": "0.00051",
        #         "price": "22349.02",
        #         "instrument_code": "BTC_EUR",
        #         "order_id": "1c6c585c-ec3d-4b94-9292-6c3d04a31dc8",
        #         "remaining": "0.00051",
        #         "channel_name": "TRADING",
        #         "type": "BOOKED",
        #         "time": "2022-07-21T12:50:10.093000Z"
        #     }
        #
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        order = self.parse_trading_order(message)
        orders = self.orders
        orders.append(order)
        client.resolve(self.orders, 'orders:' + order['symbol'])
        client.resolve(self.orders, 'orders')

    def parse_trading_order(self, order, market=None):
        #
        #     {
        #         "order_book_sequence": 892925263,
        #         "side": "BUY",
        #         "amount": "0.00046",
        #         "trade_id": "d67b9b69-ab76-480f-9ba3-b33582202836",
        #         "matched_as": "TAKER",
        #         "matched_amount": "0.00046",
        #         "matched_price": "22231.08",
        #         "instrument_code": "BTC_EUR",
        #         "order_id": "7b39f316-0a71-4bfd-adda-3062e6f0bd37",
        #         "remaining": "0.0",
        #         "channel_name": "TRADING",
        #         "type": "FILL",
        #         "time": "2022-07-21T12:41:22.883341Z"
        #     }
        #
        #     {
        #         "status": "CANCELLED",
        #         "order_book_sequence": 892928424,
        #         "amount": "0.0003",
        #         "side": "SELL",
        #         "price": "50338.65",
        #         "instrument_code": "BTC_EUR",
        #         "order_id": "b3994a08-a9e8-4a79-a08b-33e3480382df",
        #         "remaining": "0.0003",
        #         "channel_name": "TRADING",
        #         "type": "DONE",
        #         "time": "2022-07-21T12:44:24.267000Z"
        #     }
        #
        #     {
        #         "order_book_sequence": 892934476,
        #         "side": "SELL",
        #         "amount": "0.00051",
        #         "price": "22349.02",
        #         "instrument_code": "BTC_EUR",
        #         "order_id": "1c6c585c-ec3d-4b94-9292-6c3d04a31dc8",
        #         "remaining": "0.00051",
        #         "channel_name": "TRADING",
        #         "type": "BOOKED",
        #         "time": "2022-07-21T12:50:10.093000Z"
        #     }
        #
        #     {
        #         "type":"UPDATE",
        #         "channel_name": "TRADING",
        #         "instrument_code": "BTC_EUR",
        #         "order_id": "1e842f13-762a-4745-9f3b-07f1b43e7058",
        #         "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206",
        #         "time": "2020-01-11T01:01:01.999Z",
        #         "remaining": "1.23456",
        #         "order_book_sequence": 42,
        #         "status": "APPLIED",
        #         "amount": "1.35756",
        #         "amount_delta": "0.123",
        #         "modification_id": "cc0eed67-aecc-4fb4-a625-ff3890ceb4cc"
        #     }
        #  tracked
        #     {
        #         "type": "STOP_TRACKED",
        #         "channel_name": "TRADING",
        #         "instrument_code": "BTC_EUR",
        #         "order_id": "1e842f13-762a-4745-9f3b-07f1b43e7058",
        #         "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206",
        #         "time": "2020-01-11T01:01:01.999Z",
        #         "remaining": "1.23456",
        #         "order_book_sequence": 42,
        #         "trigger_price": "12345.67",
        #         "current_price": "11111.11"
        #     }
        #
        #     {
        #         "type": "STOP_TRIGGERED",
        #         "channel_name": "TRADING",
        #         "instrument_code": "BTC_EUR",
        #         "order_id": "1e842f13-762a-4745-9f3b-07f1b43e7058",
        #         "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206",
        #         "time": "2020-01-11T01:01:01.999Z",
        #         "remaining": "1.23456",
        #         "order_book_sequence": 42,
        #         "price": "13333.33"
        #     }
        #
        datetime = self.safe_string(order, 'time')
        marketId = self.safe_string(order, 'instrument_code')
        symbol = self.safe_symbol(marketId, market, '_')
        return self.safe_order({
            'id': self.safe_string(order, 'order_id'),
            'clientOrderId': self.safe_string(order, 'client_id'),
            'info': order,
            'timestamp': self.parse8601(datetime),
            'datetime': datetime,
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': None,
            'timeInForce': None,
            'postOnly': None,
            'side': self.safe_string_lower(order, 'side'),
            'price': self.safe_number_2(order, 'price', 'matched_price'),
            'stopPrice': self.safe_number(order, 'trigger_price'),
            'amount': self.safe_number(order, 'amount'),
            'cost': None,
            'average': None,
            'filled': None,
            'remaining': self.safe_string(order, 'remaining'),
            'status': self.parse_trading_order_status(self.safe_string(order, 'status')),
            'fee': None,
            'trades': None,
        }, market)

    def parse_trading_order_status(self, status):
        statuses: dict = {
            'CANCELLED': 'canceled',
            'SELF_TRADE': 'rejected',
            'FILLED_FULLY': 'closed',
            'INSUFFICIENT_FUNDS': 'rejected',
            'INSUFFICIENT_LIQUIDITY': 'rejected',
            'TIME_TO_MARKET_EXCEEDED': 'rejected',
            'LAST_PRICE_UNKNOWN': 'rejected',
        }
        return self.safe_string(statuses, status, status)

    def handle_orders(self, client: Client, message):
        #
        #  snapshot
        #     {
        #         "account_id": "4920221a-48dc-423e-b336-bb65baccc7bd",
        #         "orders": [{
        #             "order": {
        #                 "order_id": "30e2de8f-9a34-472f-bcf8-3af4b7757626",
        #                 "account_holder": "49202c1a-48dc-423e-b336-bb65baccc7bd",
        #                 "account_id": "49202c1a-48dc-423e-b336-bb65baccc7bd",
        #                 "instrument_code": "BTC_EUR",
        #                 "time": "2022-06-28T06:10:02.587345Z",
        #                 "side": "SELL",
        #                 "price": "19645.48",
        #                 "amount": "0.00052",
        #                 "filled_amount": "0.00052",
        #                 "type": "MARKET",
        #                 "sequence": 7633339971,
        #                 "status": "FILLED_FULLY",
        #                 "average_price": "19645.48",
        #                 "is_post_only": False,
        #                 "order_book_sequence": 866885897,
        #                 "time_last_updated": "2022-06-28T06:10:02.766983Z",
        #                 "update_modification_sequence": 866885897
        #             },
        #             "trades": [{
        #                 "fee": {
        #                     "fee_amount": "0.01532347",
        #                     "fee_currency": "EUR",
        #                     "fee_percentage": "0.15",
        #                     "fee_group_id": "default",
        #                     "fee_type": "TAKER",
        #                     "running_trading_volume": "0.0",
        #                     "collection_type": "STANDARD"
        #                 },
        #                 "trade": {
        #                     "trade_id": "d83e302e-0b3a-4269-aa7d-ecf007cbe577",
        #                     "order_id": "30e2de8f-9a34-472f-bcf8-3af4b7757626",
        #                     "account_holder": "49203c1a-48dc-423e-b336-bb65baccc7bd",
        #                     "account_id": "4920221a-48dc-423e-b336-bb65baccc7bd",
        #                     "amount": "0.00052",
        #                     "side": "SELL",
        #                     "instrument_code": "BTC_EUR",
        #                     "price": "19645.48",
        #                     "time": "2022-06-28T06:10:02.693246Z",
        #                     "price_tick_sequence": 0,
        #                     "sequence": 7633339971
        #                 }
        #             }]
        #         }],
        #         "channel_name": "ACCOUNT_HISTORY",
        #         "type": "INACTIVE_ORDERS_SNAPSHOT",
        #         "time": "2022-06-28T06:11:52.469242Z"
        #     }
        #
        #
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        if self.myTrades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            self.myTrades = ArrayCacheBySymbolById(limit)
        rawOrders = self.safe_value(message, 'orders', [])
        rawOrdersLength = len(rawOrders)
        if rawOrdersLength == 0:
            return
        orders = self.orders
        for i in range(0, len(rawOrders)):
            order = self.parse_order(rawOrders[i])
            symbol = self.safe_string(order, 'symbol', '')
            orders.append(order)
            client.resolve(self.orders, 'orders:' + symbol)
            rawTrades = self.safe_value(rawOrders[i], 'trades', [])
            for ii in range(0, len(rawTrades)):
                trade = self.parse_trade(rawTrades[ii])
                symbol = self.safe_string(trade, 'symbol', symbol)
                self.myTrades.append(trade)
                client.resolve(self.myTrades, 'myTrades:' + symbol)
        client.resolve(self.orders, 'orders')
        client.resolve(self.myTrades, 'myTrades')

    def handle_account_update(self, client: Client, message):
        #
        # order created
        #     {
        #         "account_id": "49302c1a-48dc-423e-b336-bb65baccc7bd",
        #         "sequence": 7658332018,
        #         "update": {
        #             "type": "ORDER_CREATED",
        #             "activity": "TRADING",
        #             "account_holder": "43202c1a-48dc-423e-b336-bb65baccc7bd",
        #             "account_id": "49202c1a-48dc-423e-b336-bb65baccc7bd",
        #             "order_id": "8893fd69-5ebd-496b-aaa4-269b4c18aa77",
        #             "time": "2022-06-29T04:33:29.661257Z",
        #             "order": {
        #                 "time_in_force": "GOOD_TILL_CANCELLED",
        #                 "is_post_only": False,
        #                 "order_id": "8892fd69-5ebd-496b-aaa4-269b4c18aa77",
        #                 "account_holder": "43202c1a-48dc-423e-b336-bb65baccc7bd",
        #                 "account_id": "49302c1a-48dc-423e-b336-bb65baccc7bd",
        #                 "instrument_code": "BTC_EUR",
        #                 "time": "2022-06-29T04:33:29.656896Z",
        #                 "side": "SELL",
        #                 "price": "50338.65",
        #                 "amount": "0.00021",
        #                 "filled_amount": "0.0",
        #                 "type": "LIMIT"
        #             },
        #             "locked": {
        #                 "currency_code": "BTC",
        #                 "amount": "0.00021",
        #                 "new_available": "0.00017",
        #                 "new_locked": "0.00021"
        #             },
        #             "id": "26e9c36a-b231-4bb0-a686-aa915a2fc9e6",
        #             "sequence": 7658332018
        #         },
        #         "channel_name": "ACCOUNT_HISTORY",
        #         "type": "ACCOUNT_UPDATE",
        #         "time": "2022-06-29T04:33:29.684517Z"
        #     }
        #
        #  order rejected
        #     {
        #         "account_id": "49302c1a-48dc-423e-b336-bb65baccc7bd",
        #         "sequence": 7658332018,
        #         "update": {
        #             "id": "d3fe6025-5b27-4df6-a957-98b8d131cb9d",
        #             "type": "ORDER_REJECTED",
        #             "activity": "TRADING",
        #             "account_id": "b355abb8-aaae-4fae-903c-c60ff74723c6",
        #             "sequence": 0,
        #             "timestamp": "2018-08-01T13:39:15.590Z",
        #             "reason": "INSUFFICIENT_FUNDS",
        #             "order_id": "6f991342-da2c-45c6-8830-8bf519cfc8cc",
        #             "client_id": "fb497387-8223-4111-87dc-66a86f98a7cf",
        #             "unlocked": {
        #                 "currency_code": "BTC",
        #                 "amount": "1.5",
        #                 "new_locked": "2.0",
        #                 "new_available": "1.5"
        #             }
        #         }
        #     }
        #
        #  order closed
        #     {
        #         "account_id": "49202c1a-48dc-423e-b336-bb65baccc7bd",
        #         "sequence": 7658471216,
        #         "update": {
        #             "type": "ORDER_CLOSED",
        #             "activity": "TRADING",
        #             "account_holder": "49202c1a-48dc-423e-b336-bb65baccc7bd",
        #             "account_id": "49202c1a-48dc-423e-b336-bb65baccc7bd",
        #             "time": "2022-06-29T04:43:57.169616Z",
        #             "order_id": "8892fd69-5ebd-496b-aaa4-269b4c18aa77",
        #             "unlocked": {
        #                 "currency_code": "BTC",
        #                 "amount": "0.00021",
        #                 "new_available": "0.00038",
        #                 "new_locked": "0.0"
        #             },
        #             "order_book_sequence": 867964191,
        #             "id": "26c5e1d7-65ba-4a11-a661-14c0130ff484",
        #             "sequence": 7658471216
        #         },
        #         "channel_name": "ACCOUNT_HISTORY",
        #         "type": "ACCOUNT_UPDATE",
        #         "time": "2022-06-29T04:43:57.182153Z"
        #     }
        #
        #  trade settled
        #     {
        #         "account_id": "49202c1a-48dc-423e-b336-bb65baccc7bd",
        #         "sequence": 7658502878,
        #         "update": {
        #             "type": "TRADE_SETTLED",
        #             "activity": "TRADING",
        #             "account_holder": "49202c1a-48dc-423e-b336-bb65baccc7bd",
        #             "account_id": "49202c1a-48dc-423e-b336-bb65baccc7bd",
        #             "time": "2022-06-29T04:46:12.933091Z",
        #             "order_id": "ad19951a-b616-401d-a062-8d0609f038a4",
        #             "order_book_sequence": 867965579,
        #             "filled_amount": "0.00052",
        #             "order": {
        #                 "amount": "0.00052",
        #                 "filled_amount": "0.00052"
        #             },
        #             "trade": {
        #                 "trade_id": "21039eb9-2df0-4227-be2d-0ea9b691ac66",
        #                 "order_id": "ad19951a-b616-401d-a062-8d0609f038a4",
        #                 "account_holder": "49202c1a-48dc-423e-b336-bb65baccc7bd",
        #                 "account_id": "49202c1a-48dc-423e-b336-bb65baccc7bd",
        #                 "amount": "0.00052",
        #                 "side": "BUY",
        #                 "instrument_code": "BTC_EUR",
        #                 "price": "19309.29",
        #                 "time": "2022-06-29T04:46:12.870581Z",
        #                 "price_tick_sequence": 0
        #             },
        #             "fee": {
        #                 "fee_amount": "0.00000078",
        #                 "fee_currency": "BTC",
        #                 "fee_percentage": "0.15",
        #                 "fee_group_id": "default",
        #                 "fee_type": "TAKER",
        #                 "running_trading_volume": "0.00052",
        #                 "collection_type": "STANDARD"
        #             },
        #             "spent": {
        #                 "currency_code": "EUR",
        #                 "amount": "10.0408308",
        #                 "new_available": "0.0",
        #                 "new_locked": "0.15949533"
        #             },
        #             "credited": {
        #                 "currency_code": "BTC",
        #                 "amount": "0.00051922",
        #                 "new_available": "0.00089922",
        #                 "new_locked": "0.0"
        #             },
        #             "unlocked": {
        #                 "currency_code": "EUR",
        #                 "amount": "0.0",
        #                 "new_available": "0.0",
        #                 "new_locked": "0.15949533"
        #             },
        #             "id": "22b40199-2508-4176-8a14-d4785c933444",
        #             "sequence": 7658502878
        #         },
        #         "channel_name": "ACCOUNT_HISTORY",
        #         "type": "ACCOUNT_UPDATE",
        #         "time": "2022-06-29T04:46:12.941837Z"
        #     }
        #
        #  Trade Settled with BEST fee collection enabled
        #     {
        #         "account_id": "49302c1a-48dc-423e-b336-bb65baccc7bd",
        #         "sequence": 7658951984,
        #         "update": {
        #             "id": "70e00504-d892-456f-9aae-4da7acb36aac",
        #             "sequence": 361792,
        #             "order_book_sequence": 123456,
        #             "type": "TRADE_SETTLED",
        #             "activity": "TRADING",
        #             "account_id": "379a12c0-4560-11e9-82fe-2b25c6f7d123",
        #             "time": "2019-10-22T12:09:55.731Z",
        #             "order_id": "9fcdd91c-7f6e-45f4-9956-61cddba55de5",
        #             "client_id": "fb497387-8223-4111-87dc-66a86f98a7cf",
        #             "order": {
        #                 "amount": "0.5",
        #                 "filled_amount": "0.5"
        #             },
        #             "trade": {
        #                 "trade_id": "a828b63e-b2cb-48f0-8d99-8fc22cf98e08",
        #                 "order_id": "9fcdd91c-7f6e-45f4-9956-61cddba55de5",
        #                 "account_id": "379a12c0-4560-11e9-82fe-2b25c6f7d123",
        #                 "amount": "0.5",
        #                 "side": "BUY",
        #                 "instrument_code": "BTC_EUR",
        #                 "price": "7451.6",
        #                 "time": "2019-10-22T12:09:55.667Z"
        #             },
        #             "fee": {
        #                 "fee_amount": "23.28625",
        #                 "fee_currency": "BEST",
        #                 "fee_percentage": "0.075",
        #                 "fee_group_id": "default",
        #                 "fee_type": "TAKER",
        #                 "running_trading_volume": "0.10058",
        #                 "collection_type": "BEST",
        #                 "applied_best_eur_rate": "1.04402"
        #             },
        #             "spent": {
        #                 "currency_code": "EUR",
        #                 "amount": "3725.8",
        #                 "new_available": "14517885.0675703028781",
        #                 "new_locked": "2354.882"
        #             },
        #             "spent_on_fees": {
        #                 "currency_code": "BEST",
        #                 "amount": "23.28625",
        #                 "new_available": "9157.31375",
        #                 "new_locked": "0.0"
        #             },
        #             "credited": {
        #                 "currency_code": "BTC",
        #                 "amount": "0.5",
        #                 "new_available": "5839.89633700481",
        #                 "new_locked": "0.0"
        #             },
        #             "unlocked": {
        #                 "currency_code": "EUR",
        #                 "amount": "0.15",
        #                 "new_available": "14517885.0675703028781",
        #                 "new_locked": "2354.882"
        #             }
        #         }
        #         "channel_name": "ACCOUNT_HISTORY",
        #         "type": "ACCOUNT_UPDATE",
        #         "time": "2022-06-29T05:18:51.760338Z"
        #     }
        #
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        if self.myTrades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            self.myTrades = ArrayCacheBySymbolById(limit)
        symbol = None
        orders = self.orders
        update = self.safe_value(message, 'update', {})
        updateType = self.safe_string(update, 'type')
        if updateType == 'ORDER_REJECTED' or updateType == 'ORDER_CLOSED' or updateType == 'STOP_ORDER_TRIGGERED':
            orderId = self.safe_string(update, 'order_id')
            datetime = self.safe_string_2(update, 'time', 'timestamp')
            previousOrderArray = self.filter_by_array(self.orders, 'id', orderId, False)
            previousOrder = self.safe_value(previousOrderArray, 0, {})
            symbol = previousOrder['symbol']
            filled = self.safe_string(update, 'filled_amount')
            status = self.parse_ws_order_status(updateType)
            if updateType == 'ORDER_CLOSED' and Precise.string_eq(filled, '0'):
                status = 'canceled'
            orderObject: dict = {
                'id': orderId,
                'symbol': symbol,
                'status': status,
                'timestamp': self.parse8601(datetime),
                'datetime': datetime,
            }
            orders.append(orderObject)
        else:
            parsed = self.parse_order(update)
            symbol = self.safe_string(parsed, 'symbol', '')
            orders.append(parsed)
        client.resolve(self.orders, 'orders:' + symbol)
        client.resolve(self.orders, 'orders')
        # update balance
        balanceKeys = ['locked', 'unlocked', 'spent', 'spent_on_fees', 'credited', 'deducted']
        for i in range(0, len(balanceKeys)):
            newBalance = self.safe_value(update, balanceKeys[i])
            if newBalance is not None:
                self.update_balance(newBalance)
        client.resolve(self.balance, 'balance')
        # update trades
        if updateType == 'TRADE_SETTLED':
            parsed = self.parse_trade(update)
            symbol = self.safe_string(parsed, 'symbol', '')
            myTrades = self.myTrades
            myTrades.append(parsed)
            client.resolve(self.myTrades, 'myTrades:' + symbol)
            client.resolve(self.myTrades, 'myTrades')

    def parse_ws_order_status(self, status):
        statuses: dict = {
            'ORDER_REJECTED': 'rejected',
            'ORDER_CLOSED': 'closed',
            'STOP_ORDER_TRIGGERED': 'triggered',
        }
        return self.safe_string(statuses, status, status)

    def update_balance(self, balance):
        #
        #     {
        #         "currency_code": "EUR",
        #         "amount": "0.0",
        #         "new_available": "0.0",
        #         "new_locked": "0.15949533"
        #     }
        #
        currencyId = self.safe_string(balance, 'currency_code')
        code = self.safe_currency_code(currencyId)
        account = self.account()
        account['free'] = self.safe_string(balance, 'new_available')
        account['used'] = self.safe_string(balance, 'new_locked')
        self.balance[code] = account
        self.balance = self.safe_balance(self.balance)

    async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """

        https://developers.bitpanda.com/exchange/#candlesticks-channel

        watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        marketId = market['id']
        url = self.urls['api']['ws']
        timeframes = self.safe_value(self.options, 'timeframes', {})
        timeframeId = self.safe_value(timeframes, timeframe)
        if timeframeId is None:
            raise NotSupported(self.id + ' self interval is not supported, please provide one of the supported timeframes')
        messageHash = 'ohlcv.' + symbol + '.' + timeframe
        subscriptionHash = 'CANDLESTICKS'
        client = self.safe_value(self.clients, url)
        type = 'SUBSCRIBE'
        subscription = {}
        if client is not None:
            subscription = self.safe_value(client.subscriptions, subscriptionHash)
            if subscription is not None:
                ohlcvMarket = self.safe_value(subscription, marketId, {})
                marketSubscribed = self.safe_bool(ohlcvMarket, timeframe, False)
                if not marketSubscribed:
                    type = 'UPDATE_SUBSCRIPTION'
                    client.subscriptions[subscriptionHash] = None
            else:
                subscription = {}
        subscriptionMarketId = self.safe_value(subscription, marketId)
        if subscriptionMarketId is None:
            subscription[marketId] = {}
        subscription[marketId][timeframe] = True
        properties = []
        marketIds = list(subscription.keys())
        for i in range(0, len(marketIds)):
            marketIdtimeframes = list(subscription[marketIds[i]].keys())
            for ii in range(0, len(marketIdtimeframes)):
                marketTimeframeId = self.safe_value(timeframes, timeframe)
                property: dict = {
                    'instrument_code': marketIds[i],
                    'time_granularity': marketTimeframeId,
                }
                properties.append(property)
        request: dict = {
            'type': type,
            'channels': [
                {
                    'name': 'CANDLESTICKS',
                    'properties': properties,
                },
            ],
        }
        ohlcv = await self.watch(url, messageHash, self.deep_extend(request, params), subscriptionHash, subscription)
        if self.newUpdates:
            limit = ohlcv.getLimit(symbol, limit)
        return self.filter_by_since_limit(ohlcv, since, limit, 0, True)

    def handle_ohlcv(self, client: Client, message):
        #
        #  snapshot
        #     {
        #         "instrument_code": "BTC_EUR",
        #         "granularity": {unit: "MONTHS", period: 1},
        #         "high": "29750.81",
        #         "low": "16764.59",
        #         "open": "29556.02",
        #         "close": "20164.55",
        #         "volume": "107518944.610659",
        #         "last_sequence": 2275507,
        #         "channel_name": "CANDLESTICKS",
        #         "type": "CANDLESTICK_SNAPSHOT",
        #         "time": "2022-06-30T23:59:59.999000Z"
        #     }
        #
        #  update
        #     {
        #         "instrument_code": "BTC_EUR",
        #         "granularity": {
        #             "unit": "MINUTES",
        #             "period": 1
        #         },
        #         "high": "20164.16",
        #         "low": "20164.16",
        #         "open": "20164.16",
        #         "close": "20164.16",
        #         "volume": "3645.2768448",
        #         "last_sequence": 2275511,
        #         "channel_name": "CANDLESTICKS",
        #         "type": "CANDLESTICK",
        #         "time": "2022-06-24T21:20:59.999000Z"
        #     }
        #
        marketId = self.safe_string(message, 'instrument_code')
        symbol = self.safe_symbol(marketId)
        dateTime = self.safe_string(message, 'time')
        timeframeId = self.safe_value(message, 'granularity')
        timeframes = self.safe_value(self.options, 'timeframes', {})
        timeframe = self.find_timeframe(timeframeId, timeframes)
        channel = 'ohlcv.' + symbol + '.' + timeframe
        parsed = [
            self.parse8601(dateTime),
            self.safe_number(message, 'open'),
            self.safe_number(message, 'high'),
            self.safe_number(message, 'low'),
            self.safe_number(message, 'close'),
            self.safe_number(message, 'volume'),
        ]
        self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
        stored = self.safe_value(self.ohlcvs[symbol], timeframe)
        if stored is None:
            limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
            stored = ArrayCacheByTimestamp(limit)
        stored.append(parsed)
        self.ohlcvs[symbol][timeframe] = stored
        client.resolve(stored, channel)

    def find_timeframe(self, timeframe, timeframes=None):
        timeframes = timeframes or self.timeframes
        keys = list(timeframes.keys())
        for i in range(0, len(keys)):
            key = keys[i]
            if timeframes[key]['unit'] == timeframe['unit'] and timeframes[key]['period'] == timeframe['period']:
                return key
        return None

    def handle_subscriptions(self, client: Client, message):
        #
        #     {
        #         "channels": [{
        #             "instrument_codes": [Array],
        #             "depth": 0,
        #             "name": "ORDER_BOOK"
        #         }],
        #         "type": "SUBSCRIPTIONS",
        #         "time": "2022-06-23T15:36:26.948282Z"
        #     }
        #
        return message

    def handle_heartbeat(self, client: Client, message):
        #
        #     {
        #         "subscription": "SYSTEM",
        #         "channel_name": "SYSTEM",
        #         "type": "HEARTBEAT",
        #         "time": "2022-06-23T16:31:49.170224Z"
        #     }
        #
        return message

    def handle_error_message(self, client: Client, message) -> Bool:
        #
        #     {
        #         "error": "MALFORMED_JSON",
        #         "channel_name": "SYSTEM",
        #         "type": "ERROR",
        #         "time": "2022-06-23T15:38:25.470391Z"
        #     }
        #
        raise ExchangeError(self.id + ' ' + self.json(message))

    def handle_message(self, client: Client, message):
        error = self.safe_value(message, 'error')
        if error is not None:
            self.handle_error_message(client, message)
            return
        type = self.safe_value(message, 'type')
        handlers: dict = {
            'ORDER_BOOK_UPDATE': self.handle_order_book,
            'ORDER_BOOK_SNAPSHOT': self.handle_order_book,
            'ACTIVE_ORDERS_SNAPSHOT': self.handle_orders,
            'INACTIVE_ORDERS_SNAPSHOT': self.handle_orders,
            'ACCOUNT_UPDATE': self.handle_account_update,
            'BALANCES_SNAPSHOT': self.handle_balance_snapshot,
            'SUBSCRIPTIONS': self.handle_subscriptions,
            'SUBSCRIPTION_UPDATED': self.handle_subscriptions,
            'PRICE_TICK': self.handle_ticker,
            'PRICE_TICK_HISTORY': self.handle_subscriptions,
            'HEARTBEAT': self.handle_heartbeat,
            'MARKET_TICKER_UPDATES': self.handle_ticker,
            'PRICE_POINT_UPDATES': self.handle_price_point_updates,
            'CANDLESTICK_SNAPSHOT': self.handle_ohlcv,
            'CANDLESTICK': self.handle_ohlcv,
            'AUTHENTICATED': self.handle_authentication_message,
            'FILL': self.handle_trading,
            'DONE': self.handle_trading,
            'BOOKED': self.handle_trading,
            'UPDATE': self.handle_trading,
            'TRACKED': self.handle_trading,
            'TRIGGERED': self.handle_trading,
            'STOP_TRACKED': self.handle_trading,
            'STOP_TRIGGERED': self.handle_trading,
        }
        handler = self.safe_value(handlers, type)
        if handler is not None:
            handler(client, message)

    def handle_price_point_updates(self, client: Client, message):
        #
        #     {
        #         "channel_name": "MARKET_TICKER",
        #         "type": "PRICE_POINT_UPDATES",
        #         "time": "2019-03-01T10:59:59.999Z",
        #         "price_updates": [{
        #                 "instrument": "BTC_EUR",
        #                 "prices": [{
        #                         "time": "2019-03-01T08:59:59.999Z",
        #                         "close_price": "3580.6"
        #                     },
        #                     ...
        #                 ]
        #             },
        #             ...
        #         ]
        #     }
        #
        return message

    def handle_authentication_message(self, client: Client, message):
        #
        #    {
        #        "channel_name": "SYSTEM",
        #        "type": "AUTHENTICATED",
        #        "time": "2022-06-24T20:45:25.447488Z"
        #    }
        #
        future = self.safe_value(client.futures, 'authenticated')
        if future is not None:
            future.resolve(True)
        return message

    async def watch_many(self, messageHash, request, subscriptionHash, symbols: Strings = [], params={}):
        marketIds = []
        numSymbols = len(symbols)
        if numSymbols == 0:
            marketIds = list(self.markets_by_id.keys())
        else:
            marketIds = self.market_ids(symbols)
        url = self.urls['api']['ws']
        client = self.safe_value(self.clients, url)
        type = 'SUBSCRIBE'
        subscription = {}
        if client is not None:
            subscription = self.safe_value(client.subscriptions, subscriptionHash)
            if subscription is not None:
                for i in range(0, len(marketIds)):
                    marketId = marketIds[i]
                    marketSubscribed = self.safe_bool(subscription, marketId, False)
                    if not marketSubscribed:
                        type = 'UPDATE_SUBSCRIPTION'
                        client.subscriptions[subscriptionHash] = None
            else:
                subscription = {}
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            subscription[marketId] = True
        request['type'] = type
        request['channels'][0]['instrument_codes'] = list(subscription.keys())
        return await self.watch(url, messageHash, self.deep_extend(request, params), subscriptionHash, subscription)

    async def authenticate(self, params={}):
        url = self.urls['api']['ws']
        client = self.client(url)
        messageHash = 'authenticated'
        future = client.reusableFuture('authenticated')
        authenticated = self.safe_value(client.subscriptions, messageHash)
        if authenticated is None:
            self.check_required_credentials()
            request: dict = {
                'type': 'AUTHENTICATE',
                'api_token': self.apiKey,
            }
            self.watch(url, messageHash, self.extend(request, params), messageHash)
        return await future
