# -*- 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 ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp
from ccxt.base.types import Any, Balances, Bool, Int, Order, OrderBook, Position, Str, Strings, Ticker, Tickers, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import NotSupported
from ccxt.base.precise import Precise


class modetrade(ccxt.async_support.modetrade):

    def describe(self) -> Any:
        return self.deep_extend(super(modetrade, self).describe(), {
            'has': {
                'ws': True,
                'watchBalance': True,
                'watchMyTrades': True,
                'watchOHLCV': True,
                'watchOrderBook': True,
                'watchOrders': True,
                'watchTicker': True,
                'watchTickers': True,
                'watchBidsAsks': True,
                'watchTrades': True,
                'watchTradesForSymbols': False,
                'watchPositions': True,
            },
            'urls': {
                'api': {
                    'ws': {
                        'public': 'wss://ws-evm.orderly.org/ws/stream',
                        'private': 'wss://ws-private-evm.orderly.org/v2/ws/private/stream',
                    },
                },
                'test': {
                    'ws': {
                        'public': 'wss://testnet-ws-evm.orderly.org/ws/stream',
                        'private': 'wss://testnet-ws-private-evm.orderly.org/v2/ws/private/stream',
                    },
                },
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
                'accountId': True,
            },
            'options': {
                'tradesLimit': 1000,
                'ordersLimit': 1000,
                'requestId': {},
                'watchPositions': {
                    'fetchPositionsSnapshot': True,  # or False
                    'awaitPositionsSnapshot': True,  # whether to wait for the positions snapshot before providing updates
                },
            },
            'streaming': {
                'ping': self.ping,
                'keepAlive': 10000,
            },
            'exceptions': {
                'ws': {
                    'exact': {
                        'Auth is needed.': AuthenticationError,
                    },
                },
            },
        })

    def request_id(self, url):
        options = self.safe_dict(self.options, 'requestId', {})
        previousValue = self.safe_integer(options, url, 0)
        newValue = self.sum(previousValue, 1)
        self.options['requestId'][url] = newValue
        return newValue

    async def watch_public(self, messageHash, message):
        # the default id
        id = 'OqdphuyCtYWxwzhxyLLjOWNdFP7sQt8RPWzmb5xY'
        if self.accountId is not None and self.accountId != '':
            id = self.accountId
        url = self.urls['api']['ws']['public'] + '/' + id
        requestId = self.request_id(url)
        subscribe: dict = {
            'id': requestId,
        }
        request = self.extend(subscribe, message)
        return await self.watch(url, messageHash, request, messageHash, subscribe)

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

        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/orderbook

        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()
        name = 'orderbook'
        market = self.market(symbol)
        topic = market['id'] + '@' + name
        request: dict = {
            'event': 'subscribe',
            'topic': topic,
        }
        message = self.extend(request, params)
        orderbook = await self.watch_public(topic, message)
        return orderbook.limit()

    def handle_order_book(self, client: Client, message):
        #
        #     {
        #         "topic": "PERP_BTC_USDC@orderbook",
        #         "ts": 1650121915308,
        #         "data": {
        #             "symbol": "PERP_BTC_USDC",
        #             "bids": [
        #                 [
        #                     0.30891,
        #                     2469.98
        #                 ]
        #             ],
        #             "asks": [
        #                 [
        #                     0.31075,
        #                     2379.63
        #                 ]
        #             ]
        #         }
        #     }
        #
        data = self.safe_dict(message, 'data', {})
        marketId = self.safe_string(data, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        topic = self.safe_string(message, 'topic')
        if not (symbol in self.orderbooks):
            self.orderbooks[symbol] = self.order_book()
        orderbook = self.orderbooks[symbol]
        timestamp = self.safe_integer(message, 'ts')
        snapshot = self.parse_order_book(data, symbol, timestamp, 'bids', 'asks')
        orderbook.reset(snapshot)
        client.resolve(orderbook, topic)

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

        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/24-hour-ticker

        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()
        name = 'ticker'
        market = self.market(symbol)
        symbol = market['symbol']
        topic = market['id'] + '@' + name
        request: dict = {
            'event': 'subscribe',
            'topic': topic,
        }
        message = self.extend(request, params)
        return await self.watch_public(topic, message)

    def parse_ws_ticker(self, ticker, market=None):
        #
        #     {
        #         "symbol": "PERP_BTC_USDC",
        #         "open": 19441.5,
        #         "close": 20147.07,
        #         "high": 20761.87,
        #         "low": 19320.54,
        #         "volume": 2481.103,
        #         "amount": 50037935.0286,
        #         "count": 3689
        #     }
        #
        return self.safe_ticker({
            'symbol': self.safe_symbol(None, 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': self.safe_string(ticker, 'open'),
            'close': self.safe_string(ticker, 'close'),
            'last': None,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_string(ticker, 'volume'),
            'quoteVolume': self.safe_string(ticker, 'amount'),
            'info': ticker,
        }, market)

    def handle_ticker(self, client: Client, message):
        #
        #     {
        #         "topic": "PERP_BTC_USDC@ticker",
        #         "ts": 1657120017000,
        #         "data": {
        #             "symbol": "PERP_BTC_USDC",
        #             "open": 19441.5,
        #             "close": 20147.07,
        #             "high": 20761.87,
        #             "low": 19320.54,
        #             "volume": 2481.103,
        #             "amount": 50037935.0286,
        #             "count": 3689
        #         }
        #     }
        #
        data = self.safe_dict(message, 'data', {})
        topic = self.safe_string(message, 'topic')
        marketId = self.safe_string(data, 'symbol')
        market = self.safe_market(marketId)
        timestamp = self.safe_integer(message, 'ts')
        data['date'] = timestamp
        ticker = self.parse_ws_ticker(data, market)
        ticker['symbol'] = market['symbol']
        self.tickers[market['symbol']] = ticker
        client.resolve(ticker, topic)
        return message

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

        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/24-hour-tickers

        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
        :param str[] symbols: 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()
        symbols = self.market_symbols(symbols)
        name = 'tickers'
        topic = name
        request: dict = {
            'event': 'subscribe',
            'topic': topic,
        }
        message = self.extend(request, params)
        tickers = await self.watch_public(topic, message)
        return self.filter_by_array(tickers, 'symbol', symbols)

    def handle_tickers(self, client: Client, message):
        #
        #     {
        #         "topic":"tickers",
        #         "ts":1618820615000,
        #         "data":[
        #             {
        #                 "symbol":"PERP_NEAR_USDC",
        #                 "open":16.297,
        #                 "close":17.183,
        #                 "high":24.707,
        #                 "low":11.997,
        #                 "volume":0,
        #                 "amount":0,
        #                 "count":0
        #             },
        #         ...
        #         ]
        #     }
        #
        topic = self.safe_string(message, 'topic')
        data = self.safe_list(message, 'data', [])
        timestamp = self.safe_integer(message, 'ts')
        result = []
        for i in range(0, len(data)):
            marketId = self.safe_string(data[i], 'symbol')
            market = self.safe_market(marketId)
            ticker = self.parse_ws_ticker(self.extend(data[i], {'date': timestamp}), market)
            self.tickers[market['symbol']] = ticker
            result.append(ticker)
        client.resolve(result, topic)

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

        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/bbos

        watches best bid & ask for symbols
        :param str[] symbols: 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()
        symbols = self.market_symbols(symbols)
        name = 'bbos'
        topic = name
        request: dict = {
            'event': 'subscribe',
            'topic': topic,
        }
        message = self.extend(request, params)
        tickers = await self.watch_public(topic, message)
        return self.filter_by_array(tickers, 'symbol', symbols)

    def handle_bid_ask(self, client: Client, message):
        #
        #     {
        #       "topic": "bbos",
        #       "ts": 1726212495000,
        #       "data": [
        #         {
        #           "symbol": "PERP_BTC_USDC",
        #           "ask": 0.16570,
        #           "askSize": 4224,
        #           "bid": 0.16553,
        #           "bidSize": 6645
        #         }
        #       ]
        #     }
        #
        topic = self.safe_string(message, 'topic')
        data = self.safe_list(message, 'data', [])
        timestamp = self.safe_integer(message, 'ts')
        result = []
        for i in range(0, len(data)):
            ticker = self.parse_ws_bid_ask(self.extend(data[i], {'ts': timestamp}))
            self.tickers[ticker['symbol']] = ticker
            result.append(ticker)
        client.resolve(result, topic)

    def parse_ws_bid_ask(self, ticker, market=None):
        marketId = self.safe_string(ticker, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = self.safe_string(market, 'symbol')
        timestamp = self.safe_integer(ticker, 'ts')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'ask': self.safe_string(ticker, 'ask'),
            'askVolume': self.safe_string(ticker, 'askSize'),
            'bid': self.safe_string(ticker, 'bid'),
            'bidVolume': self.safe_string(ticker, 'bidSize'),
            'info': ticker,
        }, market)

    async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        watches historical candlestick data containing the open, high, low, and close price, and the volume of a market

        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/k-line

        :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()
        if (timeframe != '1m') and (timeframe != '5m') and (timeframe != '15m') and (timeframe != '30m') and (timeframe != '1h') and (timeframe != '1d') and (timeframe != '1w') and (timeframe != '1M'):
            raise NotSupported(self.id + ' watchOHLCV timeframe argument must be 1m, 5m, 15m, 30m, 1h, 1d, 1w, 1M')
        market = self.market(symbol)
        interval = self.safe_string(self.timeframes, timeframe, timeframe)
        name = 'kline'
        topic = market['id'] + '@' + name + '_' + interval
        request: dict = {
            'event': 'subscribe',
            'topic': topic,
        }
        message = self.extend(request, params)
        ohlcv = await self.watch_public(topic, message)
        if self.newUpdates:
            limit = ohlcv.getLimit(market['symbol'], limit)
        return self.filter_by_since_limit(ohlcv, since, limit, 0, True)

    def handle_ohlcv(self, client: Client, message):
        #
        #     {
        #         "topic":"PERP_BTC_USDC@kline_1m",
        #         "ts":1618822432146,
        #         "data":{
        #             "symbol":"PERP_BTC_USDC",
        #             "type":"1m",
        #             "open":56948.97,
        #             "close":56891.76,
        #             "high":56948.97,
        #             "low":56889.06,
        #             "volume":44.00947568,
        #             "amount":2504584.9,
        #             "startTime":1618822380000,
        #             "endTime":1618822440000
        #         }
        #     }
        #
        data = self.safe_dict(message, 'data', {})
        topic = self.safe_string(message, 'topic')
        marketId = self.safe_string(data, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        interval = self.safe_string(data, 'type')
        timeframe = self.find_timeframe(interval)
        parsed = [
            self.safe_integer(data, 'startTime'),
            self.safe_number(data, 'open'),
            self.safe_number(data, 'high'),
            self.safe_number(data, 'low'),
            self.safe_number(data, 'close'),
            self.safe_number(data, '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)
            self.ohlcvs[symbol][timeframe] = stored
        ohlcvCache = self.ohlcvs[symbol][timeframe]
        ohlcvCache.append(parsed)
        client.resolve(ohlcvCache, topic)

    async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        watches information on multiple trades made in a market

        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/trade

        :param str symbol: unified market symbol of the market trades were made in
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trade structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/?id=trade-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        topic = market['id'] + '@trade'
        request: dict = {
            'event': 'subscribe',
            'topic': topic,
        }
        message = self.extend(request, params)
        trades = await self.watch_public(topic, message)
        if self.newUpdates:
            limit = trades.getLimit(market['symbol'], limit)
        return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)

    def handle_trade(self, client: Client, message):
        #
        # {
        #     "topic":"PERP_ADA_USDC@trade",
        #     "ts":1618820361552,
        #     "data":{
        #         "symbol":"PERP_ADA_USDC",
        #         "price":1.27988,
        #         "size":300,
        #         "side":"BUY",
        #     }
        # }
        #
        topic = self.safe_string(message, 'topic')
        timestamp = self.safe_integer(message, 'ts')
        data = self.safe_dict(message, 'data', {})
        marketId = self.safe_string(data, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        trade = self.parse_ws_trade(self.extend(data, {'timestamp': timestamp}), market)
        if not (symbol in self.trades):
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            stored = ArrayCache(limit)
            self.trades[symbol] = stored
        trades = self.trades[symbol]
        trades.append(trade)
        self.trades[symbol] = trades
        client.resolve(trades, topic)

    def parse_ws_trade(self, trade, market=None):
        #
        #     {
        #         "symbol":"PERP_ADA_USDC",
        #         "timestamp":1618820361552,
        #         "price":1.27988,
        #         "size":300,
        #         "side":"BUY",
        #     }
        # private stream
        #     {
        #         symbol: 'PERP_XRP_USDC',
        #         clientOrderId: '',
        #         orderId: 1167632251,
        #         type: 'MARKET',
        #         side: 'BUY',
        #         quantity: 20,
        #         price: 0,
        #         tradeId: '1715179456664012',
        #         executedPrice: 0.5276,
        #         executedQuantity: 20,
        #         fee: 0.006332,
        #         feeAsset: 'USDC',
        #         totalExecutedQuantity: 20,
        #         avgPrice: 0.5276,
        #         averageExecutedPrice: 0.5276,
        #         status: 'FILLED',
        #         reason: '',
        #         totalFee: 0.006332,
        #         visible: 0,
        #         visibleQuantity: 0,
        #         timestamp: 1715179456660,
        #         orderTag: 'CCXT',
        #         createdTime: 1715179456656,
        #         maker: False
        #     }
        #
        marketId = self.safe_string(trade, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        price = self.safe_string_2(trade, 'executedPrice', 'price')
        amount = self.safe_string_2(trade, 'executedQuantity', 'size')
        cost = Precise.string_mul(price, amount)
        side = self.safe_string_lower(trade, 'side')
        timestamp = self.safe_integer(trade, 'timestamp')
        takerOrMaker = None
        maker = self.safe_bool(trade, 'maker')
        if maker is not None:
            takerOrMaker = 'maker' if maker else 'taker'
        fee = None
        feeValue = self.safe_string(trade, 'fee')
        if feeValue is not None:
            fee = {
                'cost': feeValue,
                'currency': self.safe_currency_code(self.safe_string(trade, 'feeAsset')),
            }
        return self.safe_trade({
            'id': self.safe_string(trade, 'tradeId'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'order': self.safe_string(trade, 'orderId'),
            'takerOrMaker': takerOrMaker,
            'type': self.safe_string_lower(trade, 'type'),
            'fee': fee,
            'info': trade,
        }, market)

    def handle_auth(self, client: Client, message):
        #
        #     {
        #         "event": "auth",
        #         "success": True,
        #         "ts": 1657463158812
        #     }
        #
        messageHash = 'authenticated'
        success = self.safe_value(message, 'success')
        if success:
            # client.resolve(message, messageHash)
            future = self.safe_value(client.futures, 'authenticated')
            future.resolve(True)
        else:
            error = AuthenticationError(self.json(message))
            client.reject(error, messageHash)
            # allows further authentication attempts
            if messageHash in client.subscriptions:
                del client.subscriptions['authenticated']

    async def authenticate(self, params={}):
        self.check_required_credentials()
        url = self.urls['api']['ws']['private'] + '/' + self.accountId
        client = self.client(url)
        messageHash = 'authenticated'
        event = 'auth'
        future = client.reusableFuture(messageHash)
        authenticated = self.safe_value(client.subscriptions, messageHash)
        if authenticated is None:
            ts = str(self.nonce())
            auth = ts
            secret = self.secret
            if secret.find('ed25519:') >= 0:
                parts = secret.split('ed25519:')
                secret = parts[1]
            signature = self.eddsa(self.encode(auth), self.base58_to_binary(secret), 'ed25519')
            request: dict = {
                'event': event,
                'params': {
                    'orderly_key': self.apiKey,
                    'sign': signature,
                    'timestamp': ts,
                },
            }
            message = self.extend(request, params)
            self.watch(url, messageHash, message, messageHash)
        return await future

    async def watch_private(self, messageHash, message, params={}):
        await self.authenticate(params)
        url = self.urls['api']['ws']['private'] + '/' + self.accountId
        requestId = self.request_id(url)
        subscribe: dict = {
            'id': requestId,
        }
        request = self.extend(subscribe, message)
        return await self.watch(url, messageHash, request, messageHash, subscribe)

    async def watch_private_multiple(self, messageHashes, message, params={}):
        await self.authenticate(params)
        url = self.urls['api']['ws']['private'] + '/' + self.accountId
        requestId = self.request_id(url)
        subscribe: dict = {
            'id': requestId,
        }
        request = self.extend(subscribe, message)
        return await self.watch_multiple(url, messageHashes, request, messageHashes, subscribe)

    async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        watches information on multiple orders made by the user

        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/execution-report
        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/algo-execution-report

        :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 bool [params.trigger]: True if trigger order
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
        """
        await self.load_markets()
        trigger = self.safe_bool_2(params, 'stop', 'trigger', False)
        topic = 'algoexecutionreport' if (trigger) else 'executionreport'
        params = self.omit(params, ['stop', 'trigger'])
        messageHash = topic
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash += ':' + symbol
        request: dict = {
            'event': 'subscribe',
            'topic': topic,
        }
        message = self.extend(request, params)
        orders = await self.watch_private(messageHash, message)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)

    async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        watches information on multiple trades made by the user

        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/execution-report
        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/algo-execution-report

        :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 bool [params.trigger]: True if trigger order
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
        """
        await self.load_markets()
        trigger = self.safe_bool_2(params, 'stop', 'trigger', False)
        topic = 'algoexecutionreport' if (trigger) else 'executionreport'
        params = self.omit(params, 'stop')
        messageHash = 'myTrades'
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash += ':' + symbol
        request: dict = {
            'event': 'subscribe',
            'topic': topic,
        }
        message = self.extend(request, params)
        orders = await self.watch_private(messageHash, message)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)

    def parse_ws_order(self, order, market=None):
        #
        #     {
        #         "symbol": "PERP_BTC_USDT",
        #         "clientOrderId": 0,
        #         "orderId": 52952826,
        #         "type": "LIMIT",
        #         "side": "SELL",
        #         "quantity": 0.01,
        #         "price": 22000,
        #         "tradeId": 0,
        #         "executedPrice": 0,
        #         "executedQuantity": 0,
        #         "fee": 0,
        #         "feeAsset": "USDT",
        #         "totalExecutedQuantity": 0,
        #         "status": "NEW",
        #         "reason": '',
        #         "orderTag": "default",
        #         "totalFee": 0,
        #         "visible": 0.01,
        #         "timestamp": 1657515556798,
        #         "reduceOnly": False,
        #         "maker": False
        #     }
        # algo order
        #     {
        #         "symbol":"PERP_MATIC_USDC",
        #         "rootAlgoOrderId":123,
        #         "parentAlgoOrderId":123,
        #         "algoOrderId":123,
        #         "orderTag":"some tags",
        #         "algoType": "STOP",
        #         "clientOrderId":"client_id",
        #         "type":"LIMIT",
        #         "side":"BUY",
        #         "quantity":7029.0,
        #         "price":0.7699,
        #         "tradeId":0,
        #         "triggerTradePrice":0,
        #         "triggerTime":1234567,
        #         "triggered": False,
        #         "activated": False,
        #         "executedPrice":0.0,
        #         "executedQuantity":0.0,
        #         "fee":0.0,
        #         "feeAsset":"USDC",
        #         "totalExecutedQuantity":0.0,
        #         "averageExecutedQuantity":0.0,
        #         "avgPrice":0,
        #         "triggerPrice":0.0,
        #         "triggerPriceType":"STOP",
        #         "isActivated": False,
        #         "status":"NEW",
        #         "rootAlgoStatus": "FILLED",
        #         "algoStatus": "FILLED",
        #         "reason":"",
        #         "totalFee":0.0,
        #         "visible": 7029.0,
        #         "visibleQuantity":7029.0,
        #         "timestamp":1704679472448,
        #         "maker":false,
        #         "isMaker":false,
        #         "createdTime":1704679472448
        #     }
        #
        orderId = self.safe_string(order, 'orderId')
        marketId = self.safe_string(order, 'symbol')
        market = self.market(marketId)
        symbol = market['symbol']
        timestamp = self.safe_integer(order, 'timestamp')
        fee = {
            'cost': self.safe_string(order, 'totalFee'),
            'currency': self.safe_string(order, 'feeAsset'),
        }
        priceString = self.safe_string(order, 'price')
        price = self.safe_number(order, 'price')
        avgPrice = self.safe_number(order, 'avgPrice')
        if Precise.string_eq(priceString, '0') and (avgPrice is not None):
            price = avgPrice
        amount = self.safe_string(order, 'quantity')
        side = self.safe_string_lower(order, 'side')
        type = self.safe_string_lower(order, 'type')
        filled = self.safe_number(order, 'totalExecutedQuantity')
        totalExecQuantity = self.safe_string(order, 'totalExecutedQuantity')
        remaining = amount
        if Precise.string_ge(amount, totalExecQuantity):
            remaining = Precise.string_sub(remaining, totalExecQuantity)
        rawStatus = self.safe_string(order, 'status')
        status = self.parse_order_status(rawStatus)
        trades = None
        clientOrderId = self.safe_string(order, 'clientOrderId')
        triggerPrice = self.safe_number(order, 'triggerPrice')
        return self.safe_order({
            'info': order,
            'symbol': symbol,
            'id': orderId,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': timestamp,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': triggerPrice,
            'triggerPrice': triggerPrice,
            'amount': amount,
            'cost': None,
            'average': None,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
            'trades': trades,
        })

    def handle_order_update(self, client: Client, message):
        #
        #     {
        #         "topic": "executionreport",
        #         "ts": 1657515556799,
        #         "data": {
        #             "symbol": "PERP_BTC_USDT",
        #             "clientOrderId": 0,
        #             "orderId": 52952826,
        #             "type": "LIMIT",
        #             "side": "SELL",
        #             "quantity": 0.01,
        #             "price": 22000,
        #             "tradeId": 0,
        #             "executedPrice": 0,
        #             "executedQuantity": 0,
        #             "fee": 0,
        #             "feeAsset": "USDT",
        #             "totalExecutedQuantity": 0,
        #             "status": "NEW",
        #             "reason": '',
        #             "orderTag": "default",
        #             "totalFee": 0,
        #             "visible": 0.01,
        #             "timestamp": 1657515556799,
        #             "maker": False
        #         }
        #     }
        #
        topic = self.safe_string(message, 'topic')
        data = self.safe_value(message, 'data')
        if isinstance(data, list):
            # algoexecutionreport
            for i in range(0, len(data)):
                order = data[i]
                tradeId = self.omit_zero(self.safe_string(data, 'tradeId'))
                if tradeId is not None:
                    self.handle_my_trade(client, order)
                self.handle_order(client, order, topic)
        else:
            # executionreport
            tradeId = self.omit_zero(self.safe_string(data, 'tradeId'))
            if tradeId is not None:
                self.handle_my_trade(client, data)
            self.handle_order(client, data, topic)

    def handle_order(self, client: Client, message, topic):
        parsed = self.parse_ws_order(message)
        symbol = self.safe_string(parsed, 'symbol')
        orderId = self.safe_string(parsed, 'id')
        if symbol is not None:
            if self.orders is None:
                limit = self.safe_integer(self.options, 'ordersLimit', 1000)
                self.orders = ArrayCacheBySymbolById(limit)
            cachedOrders = self.orders
            orders = self.safe_dict(cachedOrders.hashmap, symbol, {})
            order = self.safe_dict(orders, orderId)
            if order is not None:
                fee = self.safe_value(order, 'fee')
                if fee is not None:
                    parsed['fee'] = fee
                fees = self.safe_list(order, 'fees')
                if fees is not None:
                    parsed['fees'] = fees
                parsed['trades'] = self.safe_list(order, 'trades')
                parsed['timestamp'] = self.safe_integer(order, 'timestamp')
                parsed['datetime'] = self.safe_string(order, 'datetime')
            cachedOrders.append(parsed)
            client.resolve(self.orders, topic)
            messageHashSymbol = topic + ':' + symbol
            client.resolve(self.orders, messageHashSymbol)

    def handle_my_trade(self, client: Client, message):
        #
        # {
        #     symbol: 'PERP_XRP_USDC',
        #     clientOrderId: '',
        #     orderId: 1167632251,
        #     type: 'MARKET',
        #     side: 'BUY',
        #     quantity: 20,
        #     price: 0,
        #     tradeId: '1715179456664012',
        #     executedPrice: 0.5276,
        #     executedQuantity: 20,
        #     fee: 0.006332,
        #     feeAsset: 'USDC',
        #     totalExecutedQuantity: 20,
        #     avgPrice: 0.5276,
        #     averageExecutedPrice: 0.5276,
        #     status: 'FILLED',
        #     reason: '',
        #     totalFee: 0.006332,
        #     visible: 0,
        #     visibleQuantity: 0,
        #     timestamp: 1715179456660,
        #     orderTag: 'CCXT',
        #     createdTime: 1715179456656,
        #     maker: False
        # }
        #
        messageHash = 'myTrades'
        marketId = self.safe_string(message, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        trade = self.parse_ws_trade(message, market)
        trades = self.myTrades
        if trades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            trades = ArrayCacheBySymbolById(limit)
            self.myTrades = trades
        trades.append(trade)
        client.resolve(trades, messageHash)
        symbolSpecificMessageHash = messageHash + ':' + symbol
        client.resolve(trades, symbolSpecificMessageHash)

    async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
        """

        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/position-push

        watch all open positions
        :param str[] [symbols]: list of unified market symbols
        :param int [since]: timestamp in ms of the earliest position to fetch
        :param int [limit]: the maximum number of positions to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
        """
        await self.load_markets()
        messageHashes = []
        symbols = self.market_symbols(symbols)
        if not self.is_empty(symbols):
            for i in range(0, len(symbols)):
                symbol = symbols[i]
                messageHashes.append('positions::' + symbol)
        else:
            messageHashes.append('positions')
        url = self.urls['api']['ws']['private'] + '/' + self.accountId
        client = self.client(url)
        self.set_positions_cache(client, symbols)
        fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True)
        awaitPositionsSnapshot = self.handle_option('watchPositions', 'awaitPositionsSnapshot', True)
        if fetchPositionsSnapshot and awaitPositionsSnapshot and self.positions is None:
            snapshot = await client.future('fetchPositionsSnapshot')
            return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True)
        request: dict = {
            'event': 'subscribe',
            'topic': 'position',
        }
        newPositions = await self.watch_private_multiple(messageHashes, request, params)
        if self.newUpdates:
            return newPositions
        return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True)

    def set_positions_cache(self, client: Client, type, symbols: Strings = None):
        fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', False)
        if fetchPositionsSnapshot:
            messageHash = 'fetchPositionsSnapshot'
            if not (messageHash in client.futures):
                client.future(messageHash)
                self.spawn(self.load_positions_snapshot, client, messageHash)
        else:
            self.positions = ArrayCacheBySymbolBySide()

    async def load_positions_snapshot(self, client, messageHash):
        positions = await self.fetch_positions()
        self.positions = ArrayCacheBySymbolBySide()
        cache = self.positions
        for i in range(0, len(positions)):
            position = positions[i]
            contracts = self.safe_string(position, 'contracts', '0')
            if Precise.string_gt(contracts, '0'):
                cache.append(position)
        # don't remove the future from the .futures cache
        if messageHash in client.futures:
            future = client.futures[messageHash]
            future.resolve(cache)
            client.resolve(cache, 'positions')

    def handle_positions(self, client, message):
        #
        #    {
        #        "topic":"position",
        #        "ts":1705292345255,
        #        "data":{
        #           "positions":[
        #              {
        #                     "symbol":"PERP_ETH_USDC",
        #                     "positionQty":3.1408,
        #                     "costPosition":5706.51952,
        #                     "lastSumUnitaryFunding":0.804,
        #                     "sumUnitaryFundingVersion":0,
        #                     "pendingLongQty":0.0,
        #                     "pendingShortQty":-1.0,
        #                     "settlePrice":1816.9,
        #                     "averageOpenPrice":1804.51490427,
        #                     "unsettledPnl":-2.79856,
        #                     "pnl24H":-338.90179488,
        #                     "fee24H":4.242423,
        #                     "markPrice":1816.2,
        #                     "estLiqPrice":0.0,
        #                     "version":179967,
        #                     "imrwithOrders":0.1,
        #                     "mmrwithOrders":0.05,
        #                     "mmr":0.05,
        #                     "imr":0.1,
        #                     "timestamp":1685154032762
        #              }
        #           ]
        #        }
        #    }
        #
        data = self.safe_dict(message, 'data', {})
        rawPositions = self.safe_list(data, 'positions', [])
        if self.positions is None:
            self.positions = ArrayCacheBySymbolBySide()
        cache = self.positions
        newPositions = []
        for i in range(0, len(rawPositions)):
            rawPosition = rawPositions[i]
            marketId = self.safe_string(rawPosition, 'symbol')
            market = self.safe_market(marketId)
            position = self.parse_ws_position(rawPosition, market)
            newPositions.append(position)
            cache.append(position)
            messageHash = 'positions::' + market['symbol']
            client.resolve(position, messageHash)
        client.resolve(newPositions, 'positions')

    def parse_ws_position(self, position, market=None):
        #
        #     {
        #         "symbol":"PERP_ETH_USDC",
        #         "positionQty":3.1408,
        #         "costPosition":5706.51952,
        #         "lastSumUnitaryFunding":0.804,
        #         "sumUnitaryFundingVersion":0,
        #         "pendingLongQty":0.0,
        #         "pendingShortQty":-1.0,
        #         "settlePrice":1816.9,
        #         "averageOpenPrice":1804.51490427,
        #         "unsettledPnl":-2.79856,
        #         "pnl24H":-338.90179488,
        #         "fee24H":4.242423,
        #         "markPrice":1816.2,
        #         "estLiqPrice":0.0,
        #         "version":179967,
        #         "imrwithOrders":0.1,
        #         "mmrwithOrders":0.05,
        #         "mmr":0.05,
        #         "imr":0.1,
        #         "timestamp":1685154032762
        #     }
        #
        contract = self.safe_string(position, 'symbol')
        market = self.safe_market(contract, market)
        size = self.safe_string(position, 'positionQty')
        side: Str = None
        if Precise.string_gt(size, '0'):
            side = 'long'
        else:
            side = 'short'
        contractSize = self.safe_string(market, 'contractSize')
        markPrice = self.safe_string(position, 'markPrice')
        timestamp = self.safe_integer(position, 'timestamp')
        entryPrice = self.safe_string(position, 'averageOpenPrice')
        unrealisedPnl = self.safe_string(position, 'unsettledPnl')
        size = Precise.string_abs(size)
        notional = Precise.string_mul(size, markPrice)
        return self.safe_position({
            'info': position,
            'id': None,
            'symbol': self.safe_string(market, 'symbol'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastUpdateTimestamp': None,
            'initialMargin': None,
            'initialMarginPercentage': None,
            'maintenanceMargin': None,
            'maintenanceMarginPercentage': None,
            'entryPrice': self.parse_number(entryPrice),
            'notional': self.parse_number(notional),
            'leverage': None,
            'unrealizedPnl': self.parse_number(unrealisedPnl),
            'contracts': self.parse_number(size),
            'contractSize': self.parse_number(contractSize),
            'marginRatio': None,
            'liquidationPrice': self.safe_number(position, 'estLiqPrice'),
            'markPrice': self.parse_number(markPrice),
            'lastPrice': None,
            'collateral': None,
            'marginMode': 'cross',
            'marginType': None,
            'side': side,
            'percentage': None,
            'hedged': None,
            'stopLossPrice': None,
            'takeProfitPrice': None,
        })

    async def watch_balance(self, params={}) -> Balances:
        """
        watch balance and get the amount of funds available for trading or funds locked in orders

        https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/balance

        :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.load_markets()
        topic = 'balance'
        messageHash = topic
        request: dict = {
            'event': 'subscribe',
            'topic': topic,
        }
        message = self.extend(request, params)
        return await self.watch_private(messageHash, message)

    def handle_balance(self, client, message):
        #
        #     {
        #         "topic":"balance",
        #         "ts":1651836695254,
        #         "data":{
        #             "balances":{
        #                 "USDC":{
        #                     "holding":5555815.47398272,
        #                     "frozen":0,
        #                     "interest":0,
        #                     "pendingShortQty":0,
        #                     "pendingExposure":0,
        #                     "pendingLongQty":0,
        #                     "pendingLongExposure":0,
        #                     "version":894,
        #                     "staked":51370692,
        #                     "unbonding":0,
        #                     "vault":0,
        #                     "averageOpenPrice":0.00000574,
        #                     "pnl24H":0,
        #                     "fee24H":0.01914,
        #                     "markPrice":0.31885
        #                 }
        #             }
        #         }
        #     }
        #
        data = self.safe_dict(message, 'data', {})
        balances = self.safe_dict(data, 'balances', {})
        keys = list(balances.keys())
        ts = self.safe_integer(message, 'ts')
        self.balance['info'] = data
        self.balance['timestamp'] = ts
        self.balance['datetime'] = self.iso8601(ts)
        for i in range(0, len(keys)):
            key = keys[i]
            value = balances[key]
            code = self.safe_currency_code(key)
            account = self.balance[code] if (code in self.balance) else self.account()
            total = self.safe_string(value, 'holding')
            used = self.safe_string(value, 'frozen')
            account['total'] = total
            account['used'] = used
            account['free'] = Precise.string_sub(total, used)
            self.balance[code] = account
        self.balance = self.safe_balance(self.balance)
        client.resolve(self.balance, 'balance')

    def handle_error_message(self, client: Client, message) -> Bool:
        #
        # {"id":"1","event":"subscribe","success":false,"ts":1710780997216,"errorMsg":"Auth is needed."}
        #
        if not ('success' in message):
            return False
        success = self.safe_bool(message, 'success')
        if success:
            return False
        errorMessage = self.safe_string(message, 'errorMsg')
        try:
            if errorMessage is not None:
                feedback = self.id + ' ' + self.json(message)
                self.throw_exactly_matched_exception(self.exceptions['exact'], errorMessage, feedback)
            return False
        except Exception as error:
            if isinstance(error, AuthenticationError):
                messageHash = 'authenticated'
                client.reject(error, messageHash)
                if messageHash in client.subscriptions:
                    del client.subscriptions[messageHash]
            else:
                client.reject(error)
            return True

    def handle_message(self, client: Client, message):
        if self.handle_error_message(client, message):
            return
        methods: dict = {
            'ping': self.handle_ping,
            'pong': self.handle_pong,
            'subscribe': self.handle_subscribe,
            'orderbook': self.handle_order_book,
            'ticker': self.handle_ticker,
            'tickers': self.handle_tickers,
            'kline': self.handle_ohlcv,
            'trade': self.handle_trade,
            'auth': self.handle_auth,
            'executionreport': self.handle_order_update,
            'algoexecutionreport': self.handle_order_update,
            'position': self.handle_positions,
            'balance': self.handle_balance,
            'bbos': self.handle_bid_ask,
        }
        event = self.safe_string(message, 'event')
        method = self.safe_value(methods, event)
        if method is not None:
            method(client, message)
            return
        topic = self.safe_string(message, 'topic')
        if topic is not None:
            method = self.safe_value(methods, topic)
            if method is not None:
                method(client, message)
                return
            splitTopic = topic.split('@')
            splitLength = len(splitTopic)
            if splitLength == 2:
                name = self.safe_string(splitTopic, 1)
                method = self.safe_value(methods, name)
                if method is not None:
                    method(client, message)
                    return
                splitName = name.split('_')
                splitNameLength = len(splitTopic)
                if splitNameLength == 2:
                    method = self.safe_value(methods, self.safe_string(splitName, 0))
                    if method is not None:
                        method(client, message)

    def ping(self, client: Client):
        return {'event': 'ping'}

    async def pong(self, client: Client, message):
        await client.send({'event': 'pong'})

    def handle_ping(self, client: Client, message):
        self.spawn(self.pong, client, message)

    def handle_pong(self, client: Client, message):
        #
        # {event: "pong", ts: 1614667590000}
        #
        client.lastPong = self.milliseconds()
        return message

    def handle_subscribe(self, client: Client, message):
        #
        #     {
        #         "id": "666888",
        #         "event": "subscribe",
        #         "success": True,
        #         "ts": 1657117712212
        #     }
        #
        return message
