<?php

namespace ccxt\pro;

// 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

use Exception; // a common import
use ccxt\ExchangeError;
use ccxt\NotSupported;
use ccxt\Precise;
use \React\Async;
use \React\Promise\PromiseInterface;

class onetrading extends \ccxt\async\onetrading {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'has' => array(
                'ws' => true,
                'watchBalance' => true,
                'watchTicker' => true,
                'watchTickers' => true,
                'watchTrades' => false,
                'watchTradesForSymbols' => false,
                'watchMyTrades' => true,
                'watchOrders' => true,
                'watchOrderBook' => true,
                'watchOHLCV' => true,
            ),
            'urls' => array(
                'api' => array(
                    'ws' => 'wss://streams.onetrading.com/',
                ),
            ),
            'options' => array(
                'bp_remaining_quota' => 200,
                'timeframes' => array(
                    '1m' => array(
                        'unit' => 'MINUTES',
                        'period' => 1,
                    ),
                    '5m' => array(
                        'unit' => 'MINUTES',
                        'period' => 5,
                    ),
                    '15m' => array(
                        'unit' => 'MINUTES',
                        'period' => 15,
                    ),
                    '30m' => array(
                        'unit' => 'MINUTES',
                        'period' => 30,
                    ),
                    '1h' => array(
                        'unit' => 'HOURS',
                        'period' => 1,
                    ),
                    '4h' => array(
                        'unit' => 'HOURS',
                        'period' => 4,
                    ),
                    '1d' => array(
                        'unit' => 'DAYS',
                        'period' => 1,
                    ),
                    '1w' => array(
                        'unit' => 'WEEKS',
                        'period' => 1,
                    ),
                    '1M' => array(
                        'unit' => 'MONTHS',
                        'period' => 1,
                    ),
                ),
            ),
            'streaming' => array(
            ),
            'exceptions' => array(
            ),
        ));
    }

    public function watch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             *
             * @see 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 {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
             */
            Async\await($this->authenticate($params));
            $url = $this->urls['api']['ws'];
            $messageHash = 'balance';
            $subscribeHash = 'ACCOUNT_HISTORY';
            $bpRemainingQuota = $this->safe_integer($this->options, 'bp_remaining_quota', 200);
            $subscribe = array(
                'type' => 'SUBSCRIBE',
                'bp_remaining_quota' => $bpRemainingQuota,
                'channels' => array(
                    array(
                        'name' => 'ACCOUNT_HISTORY',
                    ),
                ),
            );
            $request = $this->deep_extend($subscribe, $params);
            return Async\await($this->watch($url, $messageHash, $request, $subscribeHash, $request));
        }) ();
    }

    public function handle_balance_snapshot($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" => [array(
        //                 "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"
        //             }
        //         ]
        //     }
        //
        $this->balance = $this->parse_balance($message);
        $messageHash = 'balance';
        $client->resolve ($this->balance, $messageHash);
    }

    public function watch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see 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 {string} $symbol unified $symbol of the $market to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $symbol = $market['symbol'];
            $subscriptionHash = 'MARKET_TICKER';
            $messageHash = 'ticker.' . $symbol;
            $request = array(
                'type' => 'SUBSCRIBE',
                'channels' => array(
                    array(
                        'name' => 'MARKET_TICKER',
                        'price_points_mode' => 'INLINE',
                    ),
                ),
            );
            return Async\await($this->watch_many($messageHash, $request, $subscriptionHash, array( $symbol ), $params));
        }) ();
    }

    public function watch_tickers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             *
             * @see https://developers.bitpanda.com/exchange/#market-ticker-channel
             *
             * watches price $tickers, a statistical calculation with the information for all markets or those specified.
             * @param {string} $symbols unified $symbols of the markets to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an array of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            if ($symbols === null) {
                $symbols = array();
            }
            $subscriptionHash = 'MARKET_TICKER';
            $messageHash = 'tickers';
            $request = array(
                'type' => 'SUBSCRIBE',
                'channels' => array(
                    array(
                        'name' => 'MARKET_TICKER',
                        'price_points_mode' => 'INLINE',
                    ),
                ),
            );
            $tickers = Async\await($this->watch_many($messageHash, $request, $subscriptionHash, $symbols, $params));
            return $this->filter_by_array($tickers, 'symbol', $symbols);
        }) ();
    }

    public function handle_ticker(Client $client, $message) {
        //
        //     {
        //         "ticker_updates" => [array(
        //             "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 = $this->safe_value($message, 'ticker_updates', array());
        $datetime = $this->safe_string($message, 'time');
        for ($i = 0; $i < count($tickers); $i++) {
            $ticker = $tickers[$i];
            $marketId = $this->safe_string($ticker, 'instrument');
            $symbol = $this->safe_symbol($marketId);
            $this->tickers[$symbol] = $this->parse_ws_ticker($ticker);
            $timestamp = $this->parse8601($datetime);
            $this->tickers[$symbol]['timestamp'] = $timestamp;
            $this->tickers[$symbol]['datetime'] = $this->iso8601($timestamp);
            $client->resolve ($this->tickers[$symbol], 'ticker.' . $symbol);
        }
        $client->resolve ($this->tickers, 'tickers');
    }

    public function parse_ws_ticker($ticker, $market = null) {
        //
        //     {
        //         "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 = $this->safe_string($ticker, 'instrument');
        return $this->safe_ticker(array(
            'symbol' => $this->safe_symbol($marketId, $market),
            'timestamp' => null,
            'datetime' => null,
            'high' => $this->safe_string($ticker, 'high'),
            'low' => $this->safe_string($ticker, 'low'),
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => null,
            'open' => null,
            'close' => $this->safe_string($ticker, 'last_price'),
            'last' => $this->safe_string($ticker, 'last_price'),
            'previousClose' => null,
            'change' => $this->safe_string($ticker, 'price_change'),
            'percentage' => $this->safe_string($ticker, 'price_change_percentage'),
            'average' => null,
            'baseVolume' => null,
            'quoteVolume' => $this->safe_number($ticker, 'volume'),
            'info' => $ticker,
        ), $market);
    }

    public function watch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://developers.bitpanda.com/exchange/#account-history-channel
             *
             * get the list of $trades associated with the user
             * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=public-$trades trade structures~
             */
            Async\await($this->load_markets());
            $messageHash = 'myTrades';
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $symbol = $market['symbol'];
                $messageHash .= ':' . $symbol;
            }
            Async\await($this->authenticate($params));
            $url = $this->urls['api']['ws'];
            $subscribeHash = 'ACCOUNT_HISTORY';
            $bpRemainingQuota = $this->safe_integer($this->options, 'bp_remaining_quota', 200);
            $subscribe = array(
                'type' => 'SUBSCRIBE',
                'bp_remaining_quota' => $bpRemainingQuota,
                'channels' => array(
                    array(
                        'name' => 'ACCOUNT_HISTORY',
                    ),
                ),
            );
            $request = $this->deep_extend($subscribe, $params);
            $trades = Async\await($this->watch($url, $messageHash, $request, $subscribeHash, $request));
            if ($this->newUpdates) {
                $limit = $trades->getLimit ($symbol, $limit);
            }
            $trades = $this->filter_by_symbol_since_limit($trades, $symbol, $since, $limit);
            $numTrades = count($trades);
            if ($numTrades === 0) {
                return Async\await($this->watch_my_trades($symbol, $since, $limit, $params));
            }
            return $trades;
        }) ();
    }

    public function watch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             *
             * @see 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 {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by $market symbols
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $symbol = $market['symbol'];
            $messageHash = 'book:' . $symbol;
            $subscriptionHash = 'ORDER_BOOK';
            $depth = 0;
            if ($limit !== null) {
                $depth = $limit;
            }
            $request = array(
                'type' => 'SUBSCRIBE',
                'channels' => array(
                    array(
                        'name' => 'ORDER_BOOK',
                        'depth' => $depth,
                    ),
                ),
            );
            $orderbook = Async\await($this->watch_many($messageHash, $request, $subscriptionHash, array( $symbol ), $params));
            return $orderbook->limit ();
        }) ();
    }

    public function handle_order_book(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 = $this->safe_string($message, 'type');
        $marketId = $this->safe_string($message, 'instrument_code');
        $symbol = $this->safe_symbol($marketId);
        $dateTime = $this->safe_string($message, 'time');
        $timestamp = $this->parse8601($dateTime);
        $channel = 'book:' . $symbol;
        $orderbook = $this->safe_value($this->orderbooks, $symbol);
        if ($orderbook === null) {
            $orderbook = $this->order_book(array());
        }
        if ($type === 'ORDER_BOOK_SNAPSHOT') {
            $snapshot = $this->parse_order_book($message, $symbol, $timestamp, 'bids', 'asks');
            $orderbook->reset ($snapshot);
        } elseif ($type === 'ORDER_BOOK_UPDATE') {
            $changes = $this->safe_value($message, 'changes', array());
            $this->handle_deltas($orderbook, $changes);
        } else {
            throw new NotSupported($this->id . ' watchOrderBook() did not recognize $message $type ' . $type);
        }
        $orderbook['nonce'] = $timestamp;
        $orderbook['timestamp'] = $timestamp;
        $orderbook['datetime'] = $this->iso8601($timestamp);
        $this->orderbooks[$symbol] = $orderbook;
        $client->resolve ($orderbook, $channel);
    }

    public function handle_delta($orderbook, $delta) {
        //
        //   array( 'BUY', "0.053595", "0" )
        //
        $bidAsk = $this->parse_bid_ask($delta, 1, 2);
        $type = $this->safe_string($delta, 0);
        if ($type === 'BUY') {
            $bids = $orderbook['bids'];
            $bids->storeArray ($bidAsk);
        } elseif ($type === 'SELL') {
            $asks = $orderbook['asks'];
            $asks->storeArray ($bidAsk);
        } else {
            throw new NotSupported($this->id . ' watchOrderBook () received unknown change $type ' . $this->json($delta));
        }
    }

    public function handle_deltas($orderbook, $deltas) {
        //
        //    array(
        //       array( 'BUY', "0.053593", "0" ),
        //       array( 'SELL', "0.053698", "0" )
        //    )
        //
        for ($i = 0; $i < count($deltas); $i++) {
            $this->handle_delta($orderbook, $deltas[$i]);
        }
    }

    public function watch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://developers.bitpanda.com/exchange/#account-history-channel
             *
             * watches information on multiple $orders made by the user
             * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->channel] can listen to $orders using ACCOUNT_HISTORY or TRADING
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $messageHash = 'orders';
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $symbol = $market['symbol'];
                $messageHash .= ':' . $symbol;
            }
            Async\await($this->authenticate($params));
            $url = $this->urls['api']['ws'];
            $subscribeHash = $this->safe_string($params, 'channel', 'ACCOUNT_HISTORY');
            $bpRemainingQuota = $this->safe_integer($this->options, 'bp_remaining_quota', 200);
            $subscribe = array(
                'type' => 'SUBSCRIBE',
                'bp_remaining_quota' => $bpRemainingQuota,
                'channels' => array(
                    array(
                        'name' => $subscribeHash,
                    ),
                ),
            );
            $request = $this->deep_extend($subscribe, $params);
            $orders = Async\await($this->watch($url, $messageHash, $request, $subscribeHash, $request));
            if ($this->newUpdates) {
                $limit = $orders->getLimit ($symbol, $limit);
            }
            $orders = $this->filter_by_symbol_since_limit($orders, $symbol, $since, $limit);
            $numOrders = count($orders);
            if ($numOrders === 0) {
                return Async\await($this->watch_orders($symbol, $since, $limit, $params));
            }
            return $orders;
        }) ();
    }

    public function handle_trading(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 ($this->orders === null) {
            $limit = $this->safe_integer($this->options, 'ordersLimit', 1000);
            $this->orders = new ArrayCacheBySymbolById ($limit);
        }
        $order = $this->parse_trading_order($message);
        $orders = $this->orders;
        $orders->append ($order);
        $client->resolve ($this->orders, 'orders:' . $order['symbol']);
        $client->resolve ($this->orders, 'orders');
    }

    public function parse_trading_order($order, $market = null) {
        //
        //     {
        //         "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 = $this->safe_string($order, 'time');
        $marketId = $this->safe_string($order, 'instrument_code');
        $symbol = $this->safe_symbol($marketId, $market, '_');
        return $this->safe_order(array(
            'id' => $this->safe_string($order, 'order_id'),
            'clientOrderId' => $this->safe_string($order, 'client_id'),
            'info' => $order,
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => null,
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $this->safe_string_lower($order, 'side'),
            'price' => $this->safe_number_2($order, 'price', 'matched_price'),
            'stopPrice' => $this->safe_number($order, 'trigger_price'),
            'amount' => $this->safe_number($order, 'amount'),
            'cost' => null,
            'average' => null,
            'filled' => null,
            'remaining' => $this->safe_string($order, 'remaining'),
            'status' => $this->parse_trading_order_status($this->safe_string($order, 'status')),
            'fee' => null,
            'trades' => null,
        ), $market);
    }

    public function parse_trading_order_status($status) {
        $statuses = array(
            'CANCELLED' => 'canceled',
            'SELF_TRADE' => 'rejected',
            'FILLED_FULLY' => 'closed',
            'INSUFFICIENT_FUNDS' => 'rejected',
            'INSUFFICIENT_LIQUIDITY' => 'rejected',
            'TIME_TO_MARKET_EXCEEDED' => 'rejected',
            'LAST_PRICE_UNKNOWN' => 'rejected',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function handle_orders(Client $client, $message) {
        //
        //  snapshot
        //     {
        //         "account_id" => "4920221a-48dc-423e-b336-bb65baccc7bd",
        //         "orders" => [{
        //             "order" => array(
        //                 "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" => [array(
        //                 "fee" => array(
        //                     "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" => array(
        //                     "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 ($this->orders === null) {
            $limit = $this->safe_integer($this->options, 'ordersLimit', 1000);
            $this->orders = new ArrayCacheBySymbolById ($limit);
        }
        if ($this->myTrades === null) {
            $limit = $this->safe_integer($this->options, 'tradesLimit', 1000);
            $this->myTrades = new ArrayCacheBySymbolById ($limit);
        }
        $rawOrders = $this->safe_value($message, 'orders', array());
        $rawOrdersLength = count($rawOrders);
        if ($rawOrdersLength === 0) {
            return;
        }
        $orders = $this->orders;
        for ($i = 0; $i < count($rawOrders); $i++) {
            $order = $this->parse_order($rawOrders[$i]);
            $symbol = $this->safe_string($order, 'symbol', '');
            $orders->append ($order);
            $client->resolve ($this->orders, 'orders:' . $symbol);
            $rawTrades = $this->safe_value($rawOrders[$i], 'trades', array());
            for ($ii = 0; $ii < count($rawTrades); $ii++) {
                $trade = $this->parse_trade($rawTrades[$ii]);
                $symbol = $this->safe_string($trade, 'symbol', $symbol);
                $this->myTrades.append ($trade);
                $client->resolve ($this->myTrades, 'myTrades:' . $symbol);
            }
        }
        $client->resolve ($this->orders, 'orders');
        $client->resolve ($this->myTrades, 'myTrades');
    }

    public function handle_account_update(Client $client, $message) {
        //
        // order created
        //     {
        //         "account_id" => "49302c1a-48dc-423e-b336-bb65baccc7bd",
        //         "sequence" => 7658332018,
        //         "update" => array(
        //             "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" => array(
        //                 "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" => array(
        //                 "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" => array(
        //             "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" => array(
        //                 "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" => array(
        //             "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" => array(
        //                 "amount" => "0.00052",
        //                 "filled_amount" => "0.00052"
        //             ),
        //             "trade" => array(
        //                 "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" => array(
        //                 "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" => array(
        //                 "currency_code" => "EUR",
        //                 "amount" => "10.0408308",
        //                 "new_available" => "0.0",
        //                 "new_locked" => "0.15949533"
        //             ),
        //             "credited" => array(
        //                 "currency_code" => "BTC",
        //                 "amount" => "0.00051922",
        //                 "new_available" => "0.00089922",
        //                 "new_locked" => "0.0"
        //             ),
        //             "unlocked" => array(
        //                 "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" => array(
        //                 "amount" => "0.5",
        //                 "filled_amount" => "0.5"
        //             ),
        //             "trade" => array(
        //                 "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" => array(
        //                 "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" => array(
        //                 "currency_code" => "EUR",
        //                 "amount" => "3725.8",
        //                 "new_available" => "14517885.0675703028781",
        //                 "new_locked" => "2354.882"
        //             ),
        //             "spent_on_fees" => array(
        //                 "currency_code" => "BEST",
        //                 "amount" => "23.28625",
        //                 "new_available" => "9157.31375",
        //                 "new_locked" => "0.0"
        //             ),
        //             "credited" => array(
        //                 "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 ($this->orders === null) {
            $limit = $this->safe_integer($this->options, 'ordersLimit', 1000);
            $this->orders = new ArrayCacheBySymbolById ($limit);
        }
        if ($this->myTrades === null) {
            $limit = $this->safe_integer($this->options, 'tradesLimit', 1000);
            $this->myTrades = new ArrayCacheBySymbolById ($limit);
        }
        $symbol = null;
        $orders = $this->orders;
        $update = $this->safe_value($message, 'update', array());
        $updateType = $this->safe_string($update, 'type');
        if ($updateType === 'ORDER_REJECTED' || $updateType === 'ORDER_CLOSED' || $updateType === 'STOP_ORDER_TRIGGERED') {
            $orderId = $this->safe_string($update, 'order_id');
            $datetime = $this->safe_string_2($update, 'time', 'timestamp');
            $previousOrderArray = $this->filter_by_array($this->orders, 'id', $orderId, false);
            $previousOrder = $this->safe_value($previousOrderArray, 0, array());
            $symbol = $previousOrder['symbol'];
            $filled = $this->safe_string($update, 'filled_amount');
            $status = $this->parse_ws_order_status($updateType);
            if ($updateType === 'ORDER_CLOSED' && Precise::string_eq($filled, '0')) {
                $status = 'canceled';
            }
            $orderObject = array(
                'id' => $orderId,
                'symbol' => $symbol,
                'status' => $status,
                'timestamp' => $this->parse8601($datetime),
                'datetime' => $datetime,
            );
            $orders->append ($orderObject);
        } else {
            $parsed = $this->parse_order($update);
            $symbol = $this->safe_string($parsed, 'symbol', '');
            $orders->append ($parsed);
        }
        $client->resolve ($this->orders, 'orders:' . $symbol);
        $client->resolve ($this->orders, 'orders');
        // $update balance
        $balanceKeys = array( 'locked', 'unlocked', 'spent', 'spent_on_fees', 'credited', 'deducted' );
        for ($i = 0; $i < count($balanceKeys); $i++) {
            $newBalance = $this->safe_value($update, $balanceKeys[$i]);
            if ($newBalance !== null) {
                $this->update_balance($newBalance);
            }
        }
        $client->resolve ($this->balance, 'balance');
        // $update trades
        if ($updateType === 'TRADE_SETTLED') {
            $parsed = $this->parse_trade($update);
            $symbol = $this->safe_string($parsed, 'symbol', '');
            $myTrades = $this->myTrades;
            $myTrades->append ($parsed);
            $client->resolve ($this->myTrades, 'myTrades:' . $symbol);
            $client->resolve ($this->myTrades, 'myTrades');
        }
    }

    public function parse_ws_order_status($status) {
        $statuses = array(
            'ORDER_REJECTED' => 'rejected',
            'ORDER_CLOSED' => 'closed',
            'STOP_ORDER_TRIGGERED' => 'triggered',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function update_balance($balance) {
        //
        //     {
        //         "currency_code" => "EUR",
        //         "amount" => "0.0",
        //         "new_available" => "0.0",
        //         "new_locked" => "0.15949533"
        //     }
        //
        $currencyId = $this->safe_string($balance, 'currency_code');
        $code = $this->safe_currency_code($currencyId);
        $account = $this->account();
        $account['free'] = $this->safe_string($balance, 'new_available');
        $account['used'] = $this->safe_string($balance, 'new_locked');
        $this->balance[$code] = $account;
        $this->balance = $this->safe_balance($this->balance);
    }

    public function watch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             *
             * @see 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 {string} $symbol unified $symbol of the $market to fetch OHLCV data for
             * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $symbol = $market['symbol'];
            $marketId = $market['id'];
            $url = $this->urls['api']['ws'];
            $timeframes = $this->safe_value($this->options, 'timeframes', array());
            $timeframeId = $this->safe_value($timeframes, $timeframe);
            if ($timeframeId === null) {
                throw new NotSupported($this->id . ' this interval is not supported, please provide one of the supported timeframes');
            }
            $messageHash = 'ohlcv.' . $symbol . '.' . $timeframe;
            $subscriptionHash = 'CANDLESTICKS';
            $client = $this->safe_value($this->clients, $url);
            $type = 'SUBSCRIBE';
            $subscription = array();
            if ($client !== null) {
                $subscription = $this->safe_value($client->subscriptions, $subscriptionHash);
                if ($subscription !== null) {
                    $ohlcvMarket = $this->safe_value($subscription, $marketId, array());
                    $marketSubscribed = $this->safe_bool($ohlcvMarket, $timeframe, false);
                    if (!$marketSubscribed) {
                        $type = 'UPDATE_SUBSCRIPTION';
                        $client->subscriptions[$subscriptionHash] = null;
                    }
                } else {
                    $subscription = array();
                }
            }
            $subscriptionMarketId = $this->safe_value($subscription, $marketId);
            if ($subscriptionMarketId === null) {
                $subscription[$marketId] = array();
            }
            $subscription[$marketId][$timeframe] = true;
            $properties = array();
            $marketIds = is_array($subscription) ? array_keys($subscription) : array();
            for ($i = 0; $i < count($marketIds); $i++) {
                $marketIdtimeframes = is_array($subscription[$marketIds[$i]]) ? array_keys($subscription[$marketIds[$i]]) : array();
                for ($ii = 0; $ii < count($marketIdtimeframes); $ii++) {
                    $marketTimeframeId = $this->safe_value($timeframes, $timeframe);
                    $property = array(
                        'instrument_code' => $marketIds[$i],
                        'time_granularity' => $marketTimeframeId,
                    );
                    $properties[] = $property;
                }
            }
            $request = array(
                'type' => $type,
                'channels' => array(
                    array(
                        'name' => 'CANDLESTICKS',
                        'properties' => $properties,
                    ),
                ),
            );
            $ohlcv = Async\await($this->watch($url, $messageHash, $this->deep_extend($request, $params), $subscriptionHash, $subscription));
            if ($this->newUpdates) {
                $limit = $ohlcv->getLimit ($symbol, $limit);
            }
            return $this->filter_by_since_limit($ohlcv, $since, $limit, 0, true);
        }) ();
    }

    public function handle_ohlcv(Client $client, $message) {
        //
        //  snapshot
        //     {
        //         "instrument_code" => "BTC_EUR",
        //         "granularity" => array( 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" => array(
        //             "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 = $this->safe_string($message, 'instrument_code');
        $symbol = $this->safe_symbol($marketId);
        $dateTime = $this->safe_string($message, 'time');
        $timeframeId = $this->safe_value($message, 'granularity');
        $timeframes = $this->safe_value($this->options, 'timeframes', array());
        $timeframe = $this->find_timeframe($timeframeId, $timeframes);
        $channel = 'ohlcv.' . $symbol . '.' . $timeframe;
        $parsed = array(
            $this->parse8601($dateTime),
            $this->safe_number($message, 'open'),
            $this->safe_number($message, 'high'),
            $this->safe_number($message, 'low'),
            $this->safe_number($message, 'close'),
            $this->safe_number($message, 'volume'),
        );
        $this->ohlcvs[$symbol] = $this->safe_value($this->ohlcvs, $symbol, array());
        $stored = $this->safe_value($this->ohlcvs[$symbol], $timeframe);
        if ($stored === null) {
            $limit = $this->safe_integer($this->options, 'OHLCVLimit', 1000);
            $stored = new ArrayCacheByTimestamp ($limit);
        }
        $stored->append ($parsed);
        $this->ohlcvs[$symbol][$timeframe] = $stored;
        $client->resolve ($stored, $channel);
    }

    public function find_timeframe($timeframe, $timeframes = null) {
        $timeframes = $timeframes || $this->timeframes;
        $keys = is_array($timeframes) ? array_keys($timeframes) : array();
        for ($i = 0; $i < count($keys); $i++) {
            $key = $keys[$i];
            if ($timeframes[$key]['unit'] === $timeframe['unit'] && $timeframes[$key]['period'] === $timeframe['period']) {
                return $key;
            }
        }
        return null;
    }

    public function handle_subscriptions(Client $client, $message) {
        //
        //     {
        //         "channels" => [array(
        //             "instrument_codes" => [Array],
        //             "depth" => 0,
        //             "name" => "ORDER_BOOK"
        //         )],
        //         "type" => "SUBSCRIPTIONS",
        //         "time" => "2022-06-23T15:36:26.948282Z"
        //     }
        //
        return $message;
    }

    public function handle_heartbeat(Client $client, $message) {
        //
        //     {
        //         "subscription" => "SYSTEM",
        //         "channel_name" => "SYSTEM",
        //         "type" => "HEARTBEAT",
        //         "time" => "2022-06-23T16:31:49.170224Z"
        //     }
        //
        return $message;
    }

    public function handle_error_message(Client $client, $message): Bool {
        //
        //     {
        //         "error" => "MALFORMED_JSON",
        //         "channel_name" => "SYSTEM",
        //         "type" => "ERROR",
        //         "time" => "2022-06-23T15:38:25.470391Z"
        //     }
        //
        throw new ExchangeError($this->id . ' ' . $this->json($message));
    }

    public function handle_message(Client $client, $message) {
        $error = $this->safe_value($message, 'error');
        if ($error !== null) {
            $this->handle_error_message($client, $message);
            return;
        }
        $type = $this->safe_value($message, 'type');
        $handlers = array(
            'ORDER_BOOK_UPDATE' => array($this, 'handle_order_book'),
            'ORDER_BOOK_SNAPSHOT' => array($this, 'handle_order_book'),
            'ACTIVE_ORDERS_SNAPSHOT' => array($this, 'handle_orders'),
            'INACTIVE_ORDERS_SNAPSHOT' => array($this, 'handle_orders'),
            'ACCOUNT_UPDATE' => array($this, 'handle_account_update'),
            'BALANCES_SNAPSHOT' => array($this, 'handle_balance_snapshot'),
            'SUBSCRIPTIONS' => array($this, 'handle_subscriptions'),
            'SUBSCRIPTION_UPDATED' => array($this, 'handle_subscriptions'),
            'PRICE_TICK' => array($this, 'handle_ticker'),
            'PRICE_TICK_HISTORY' => array($this, 'handle_subscriptions'),
            'HEARTBEAT' => array($this, 'handle_heartbeat'),
            'MARKET_TICKER_UPDATES' => array($this, 'handle_ticker'),
            'PRICE_POINT_UPDATES' => array($this, 'handle_price_point_updates'),
            'CANDLESTICK_SNAPSHOT' => array($this, 'handle_ohlcv'),
            'CANDLESTICK' => array($this, 'handle_ohlcv'),
            'AUTHENTICATED' => array($this, 'handle_authentication_message'),
            'FILL' => array($this, 'handle_trading'),
            'DONE' => array($this, 'handle_trading'),
            'BOOKED' => array($this, 'handle_trading'),
            'UPDATE' => array($this, 'handle_trading'),
            'TRACKED' => array($this, 'handle_trading'),
            'TRIGGERED' => array($this, 'handle_trading'),
            'STOP_TRACKED' => array($this, 'handle_trading'),
            'STOP_TRIGGERED' => array($this, 'handle_trading'),
        );
        $handler = $this->safe_value($handlers, $type);
        if ($handler !== null) {
            $handler($client, $message);
        }
    }

    public function handle_price_point_updates(Client $client, $message) {
        //
        //     {
        //         "channel_name" => "MARKET_TICKER",
        //         "type" => "PRICE_POINT_UPDATES",
        //         "time" => "2019-03-01T10:59:59.999Z",
        //         "price_updates" => [array(
        //                 "instrument" => "BTC_EUR",
        //                 "prices" => [array(
        //                         "time" => "2019-03-01T08:59:59.999Z",
        //                         "close_price" => "3580.6"
        //                     ),
        //                     ...
        //                 ]
        //             ),
        //             ...
        //         ]
        //     }
        //
        return $message;
    }

    public function handle_authentication_message(Client $client, $message) {
        //
        //    {
        //        "channel_name" => "SYSTEM",
        //        "type" => "AUTHENTICATED",
        //        "time" => "2022-06-24T20:45:25.447488Z"
        //    }
        //
        $future = $this->safe_value($client->futures, 'authenticated');
        if ($future !== null) {
            $future->resolve (true);
        }
        return $message;
    }

    public function watch_many($messageHash, $request, $subscriptionHash, ?array $symbols = [], $params = array ()) {
        return Async\async(function () use ($messageHash, $request, $subscriptionHash, $symbols, $params) {
            $marketIds = array();
            $numSymbols = count($symbols);
            if ($numSymbols === 0) {
                $marketIds = is_array($this->markets_by_id) ? array_keys($this->markets_by_id) : array();
            } else {
                $marketIds = $this->market_ids($symbols);
            }
            $url = $this->urls['api']['ws'];
            $client = $this->safe_value($this->clients, $url);
            $type = 'SUBSCRIBE';
            $subscription = array();
            if ($client !== null) {
                $subscription = $this->safe_value($client->subscriptions, $subscriptionHash);
                if ($subscription !== null) {
                    for ($i = 0; $i < count($marketIds); $i++) {
                        $marketId = $marketIds[$i];
                        $marketSubscribed = $this->safe_bool($subscription, $marketId, false);
                        if (!$marketSubscribed) {
                            $type = 'UPDATE_SUBSCRIPTION';
                            $client->subscriptions[$subscriptionHash] = null;
                        }
                    }
                } else {
                    $subscription = array();
                }
            }
            for ($i = 0; $i < count($marketIds); $i++) {
                $marketId = $marketIds[$i];
                $subscription[$marketId] = true;
            }
            $request['type'] = $type;
            $request['channels'][0]['instrument_codes'] = is_array($subscription) ? array_keys($subscription) : array();
            return Async\await($this->watch($url, $messageHash, $this->deep_extend($request, $params), $subscriptionHash, $subscription));
        }) ();
    }

    public function authenticate($params = array ()) {
        return Async\async(function () use ($params) {
            $url = $this->urls['api']['ws'];
            $client = $this->client($url);
            $messageHash = 'authenticated';
            $future = $client->reusableFuture ('authenticated');
            $authenticated = $this->safe_value($client->subscriptions, $messageHash);
            if ($authenticated === null) {
                $this->check_required_credentials();
                $request = array(
                    'type' => 'AUTHENTICATE',
                    'api_token' => $this->apiKey,
                );
                $this->watch($url, $messageHash, $this->extend($request, $params), $messageHash);
            }
            return Async\await($future);
        }) ();
    }
}
