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

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

import ccxt_zagrava.async_support
from ccxt_zagrava.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp
import hashlib
from ccxt_zagrava.base.types import Balances, Int, Order, OrderBook, Position, Str, Strings, Ticker, Tickers, Trade
from ccxt_zagrava.async_support.base.ws.client import Client
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import ArgumentsRequired
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import RateLimitExceeded
from ccxt_zagrava.base.errors import InvalidNonce
from ccxt_zagrava.base.precise import Precise


class bitget(ccxt_zagrava.async_support.bitget):

    def describe(self):
        return self.deep_extend(super(bitget, self).describe(), {
            'has': {
                'ws': True,
                'createOrderWs': False,
                'editOrderWs': False,
                'fetchOpenOrdersWs': False,
                'fetchOrderWs': False,
                'cancelOrderWs': False,
                'cancelOrdersWs': False,
                'cancelAllOrdersWs': False,
                'watchBalance': True,
                'watchMyTrades': True,
                'watchOHLCV': True,
                'watchOHLCVForSymbols': False,
                'watchOrderBook': True,
                'watchOrderBookForSymbols': True,
                'watchOrders': True,
                'watchTicker': True,
                'watchTickers': True,
                'watchTrades': True,
                'watchTradesForSymbols': True,
                'watchPositions': True,
            },
            'urls': {
                'api': {
                    'ws': {
                        'public': 'wss://ws.bitget.com/v2/ws/public',
                        'private': 'wss://ws.bitget.com/v2/ws/private',
                    },
                },
            },
            'options': {
                'tradesLimit': 1000,
                'OHLCVLimit': 1000,
                # WS timeframes differ from REST timeframes
                'timeframes': {
                    '1m': '1m',
                    '5m': '5m',
                    '15m': '15m',
                    '30m': '30m',
                    '1h': '1H',
                    '4h': '4H',
                    '6h': '6H',
                    '12h': '12H',
                    '1d': '1D',
                    '1w': '1W',
                },
            },
            'streaming': {
                'ping': self.ping,
            },
            'exceptions': {
                'ws': {
                    'exact': {
                        '30001': BadRequest,  # {"event":"error","code":30001,"msg":"instType:sp,channel:candleNone,instId:BTCUSDT doesn't exist"}
                        '30002': AuthenticationError,  # illegal request
                        '30003': BadRequest,  # invalid op
                        '30004': AuthenticationError,  # requires login
                        '30005': AuthenticationError,  # login failed
                        '30006': RateLimitExceeded,  # too many requests
                        '30007': RateLimitExceeded,  # request over limit,connection close
                        '30011': AuthenticationError,  # invalid ACCESS_KEY
                        '30012': AuthenticationError,  # invalid ACCESS_PASSPHRASE
                        '30013': AuthenticationError,  # invalid ACCESS_TIMESTAMP
                        '30014': BadRequest,  # Request timestamp expired
                        '30015': AuthenticationError,  # {event: 'error', code: 30015, msg: 'Invalid sign'}
                        '30016': BadRequest,  # {event: 'error', code: 30016, msg: 'Param error'}
                    },
                    'broad': {},
                },
            },
        })

    def get_inst_type(self, market, params={}):
        instType = None
        if market is None:
            instType, params = self.handleProductTypeAndParams(None, params)
        elif (market['swap']) or (market['future']):
            instType, params = self.handleProductTypeAndParams(market, params)
        else:
            instType = 'SPOT'
        instypeAux = None
        instypeAux, params = self.handle_option_and_params(params, 'getInstType', 'instType', instType)
        instType = instypeAux
        return [instType, params]

    async def watch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :see: https://www.bitget.com/api-doc/spot/websocket/public/Tickers-Channel
        :see: https://www.bitget.com/api-doc/contract/websocket/public/Tickers-Channel
        :param str symbol: unified symbol of the market to watch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        messageHash = 'ticker:' + symbol
        instType = None
        instType, params = self.get_inst_type(market, params)
        args: dict = {
            'instType': instType,
            'channel': 'ticker',
            'instId': market['id'],
        }
        return await self.watch_public(messageHash, args, params)

    async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
        :see: https://www.bitget.com/api-doc/spot/websocket/public/Tickers-Channel
        :see: https://www.bitget.com/api-doc/contract/websocket/public/Tickers-Channel
        :param str[] symbols: unified symbol of the market to watch the tickers for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols, None, False)
        market = self.market(symbols[0])
        instType = None
        instType, params = self.get_inst_type(market, params)
        topics = []
        messageHashes = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            marketInner = self.market(symbol)
            args: dict = {
                'instType': instType,
                'channel': 'ticker',
                'instId': marketInner['id'],
            }
            topics.append(args)
            messageHashes.append('ticker:' + symbol)
        tickers = await self.watch_public_multiple(messageHashes, topics, params)
        if self.newUpdates:
            result: dict = {}
            result[tickers['symbol']] = tickers
            return result
        return self.filter_by_array(self.tickers, 'symbol', symbols)

    def handle_ticker(self, client: Client, message):
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {
        #             "instType": "SPOT",
        #             "channel": "ticker",
        #             "instId": "BTCUSDT"
        #         },
        #         "data": [
        #             {
        #                 "instId": "BTCUSDT",
        #                 "lastPr": "43528.19",
        #                 "open24h": "42267.78",
        #                 "high24h": "44490.00",
        #                 "low24h": "41401.53",
        #                 "change24h": "0.03879",
        #                 "bidPr": "43528",
        #                 "askPr": "43528.01",
        #                 "bidSz": "0.0334",
        #                 "askSz": "0.1917",
        #                 "baseVolume": "15002.4216",
        #                 "quoteVolume": "648006446.7164",
        #                 "openUtc": "44071.18",
        #                 "changeUtc24h": "-0.01232",
        #                 "ts": "1701842994338"
        #             }
        #         ],
        #         "ts": 1701842994341
        #     }
        #
        ticker = self.parse_ws_ticker(message)
        symbol = ticker['symbol']
        self.tickers[symbol] = ticker
        messageHash = 'ticker:' + symbol
        client.resolve(ticker, messageHash)

    def parse_ws_ticker(self, message, market=None):
        #
        # spot
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {
        #             "instType": "SPOT",
        #             "channel": "ticker",
        #             "instId": "BTCUSDT"
        #         },
        #         "data": [
        #             {
        #                 "instId": "BTCUSDT",
        #                 "lastPr": "43528.19",
        #                 "open24h": "42267.78",
        #                 "high24h": "44490.00",
        #                 "low24h": "41401.53",
        #                 "change24h": "0.03879",
        #                 "bidPr": "43528",
        #                 "askPr": "43528.01",
        #                 "bidSz": "0.0334",
        #                 "askSz": "0.1917",
        #                 "baseVolume": "15002.4216",
        #                 "quoteVolume": "648006446.7164",
        #                 "openUtc": "44071.18",
        #                 "changeUtc24h": "-0.01232",
        #                 "ts": "1701842994338"
        #             }
        #         ],
        #         "ts": 1701842994341
        #     }
        #
        # contract
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {
        #             "instType": "USDT-FUTURES",
        #             "channel": "ticker",
        #             "instId": "BTCUSDT"
        #         },
        #         "data": [
        #             {
        #                 "instId": "BTCUSDT",
        #                 "lastPr": "43480.4",
        #                 "bidPr": "43476.3",
        #                 "askPr": "43476.8",
        #                 "bidSz": "0.1",
        #                 "askSz": "3.055",
        #                 "open24h": "42252.3",
        #                 "high24h": "44518.2",
        #                 "low24h": "41387.0",
        #                 "change24h": "0.03875",
        #                 "fundingRate": "0.000096",
        #                 "nextFundingTime": "1701849600000",
        #                 "markPrice": "43476.4",
        #                 "indexPrice": "43478.4",
        #                 "holdingAmount": "50670.787",
        #                 "baseVolume": "120187.104",
        #                 "quoteVolume": "5167385048.693",
        #                 "openUtc": "44071.4",
        #                 "symbolType": "1",
        #                 "symbol": "BTCUSDT",
        #                 "deliveryPrice": "0",
        #                 "ts": "1701843962811"
        #             }
        #         ],
        #         "ts": 1701843962812
        #     }
        #
        arg = self.safe_value(message, 'arg', {})
        data = self.safe_value(message, 'data', [])
        ticker = self.safe_value(data, 0, {})
        timestamp = self.safe_integer(ticker, 'ts')
        instType = self.safe_string(arg, 'instType')
        marketType = 'spot' if (instType == 'SPOT') else 'contract'
        marketId = self.safe_string(ticker, 'instId')
        market = self.safe_market(marketId, market, None, marketType)
        close = self.safe_string(ticker, 'lastPr')
        changeDecimal = self.safe_string(ticker, 'change24h')
        change = Precise.string_mul(changeDecimal, '100')
        return self.safe_ticker({
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high24h'),
            'low': self.safe_string(ticker, 'low24h'),
            'bid': self.safe_string(ticker, 'bidPr'),
            'bidVolume': self.safe_string(ticker, 'bidSz'),
            'ask': self.safe_string(ticker, 'askPr'),
            'askVolume': self.safe_string(ticker, 'askSz'),
            'vwap': None,
            'open': self.safe_string(ticker, 'open24h'),
            'close': close,
            'last': close,
            'previousClose': None,
            'change': None,
            'percentage': change,
            'average': None,
            'baseVolume': self.safe_string(ticker, 'baseVolume'),
            'quoteVolume': self.safe_string(ticker, 'quoteVolume'),
            'info': ticker,
        }, market)

    async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        watches historical candlestick data containing the open, high, low, close price, and the volume of a market
        :see: https://www.bitget.com/api-doc/spot/websocket/public/Candlesticks-Channel
        :see: https://www.bitget.com/api-doc/contract/websocket/public/Candlesticks-Channel
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        timeframes = self.safe_value(self.options, 'timeframes')
        interval = self.safe_string(timeframes, timeframe)
        messageHash = 'candles:' + timeframe + ':' + symbol
        instType = None
        instType, params = self.get_inst_type(market, params)
        args: dict = {
            'instType': instType,
            'channel': 'candle' + interval,
            'instId': market['id'],
        }
        ohlcv = await self.watch_public(messageHash, args, params)
        if self.newUpdates:
            limit = ohlcv.getLimit(symbol, limit)
        return self.filter_by_since_limit(ohlcv, since, limit, 0, True)

    def handle_ohlcv(self, client: Client, message):
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {
        #             "instType": "SPOT",
        #             "channel": "candle1m",
        #             "instId": "BTCUSDT"
        #         },
        #         "data": [
        #             [
        #                 "1701871620000",
        #                 "44080.23",
        #                 "44080.23",
        #                 "44028.5",
        #                 "44028.51",
        #                 "9.9287",
        #                 "437404.105512",
        #                 "437404.105512"
        #             ],
        #             [
        #                 "1701871680000",
        #                 "44028.51",
        #                 "44108.11",
        #                 "44028.5",
        #                 "44108.11",
        #                 "17.139",
        #                 "755436.870643",
        #                 "755436.870643"
        #             ],
        #         ],
        #         "ts": 1701901610417
        #     }
        #
        arg = self.safe_value(message, 'arg', {})
        instType = self.safe_string(arg, 'instType')
        marketType = 'spot' if (instType == 'SPOT') else 'contract'
        marketId = self.safe_string(arg, 'instId')
        market = self.safe_market(marketId, None, None, marketType)
        symbol = market['symbol']
        self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
        channel = self.safe_string(arg, 'channel')
        interval = channel.replace('candle', '')
        timeframes = self.safe_value(self.options, 'timeframes')
        timeframe = self.find_timeframe(interval, timeframes)
        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
        data = self.safe_value(message, 'data', [])
        for i in range(0, len(data)):
            parsed = self.parse_ws_ohlcv(data[i], market)
            stored.append(parsed)
        messageHash = 'candles:' + timeframe + ':' + symbol
        client.resolve(stored, messageHash)

    def parse_ws_ohlcv(self, ohlcv, market=None) -> list:
        #
        #     [
        #         "1701871620000",  # timestamp
        #         "44080.23",  # open
        #         "44080.23",  # high
        #         "44028.5",  # low
        #         "44028.51",  # close
        #         "9.9287",  # base volume
        #         "437404.105512",  # quote volume
        #         "437404.105512"  # USDT volume
        #     ]
        #
        volumeIndex = 6 if (market['inverse']) else 5
        return [
            self.safe_integer(ohlcv, 0),
            self.safe_number(ohlcv, 1),
            self.safe_number(ohlcv, 2),
            self.safe_number(ohlcv, 3),
            self.safe_number(ohlcv, 4),
            self.safe_number(ohlcv, volumeIndex),
        ]

    async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :see: https://www.bitget.com/api-doc/spot/websocket/public/Depth-Channel
        :see: https://www.bitget.com/api-doc/contract/websocket/public/Order-Book-Channel
        :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_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        return await self.watch_order_book_for_symbols([symbol], limit, params)

    async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
        """
        watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :see: https://www.bitget.com/api-doc/spot/websocket/public/Depth-Channel
        :see: https://www.bitget.com/api-doc/contract/websocket/public/Order-Book-Channel
        :param str[] symbols: unified array of symbols
        :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_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        channel = 'books'
        incrementalFeed = True
        if (limit == 1) or (limit == 5) or (limit == 15):
            channel += str(limit)
            incrementalFeed = False
        topics = []
        messageHashes = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.market(symbol)
            instType = None
            instType, params = self.get_inst_type(market, params)
            args: dict = {
                'instType': instType,
                'channel': channel,
                'instId': market['id'],
            }
            topics.append(args)
            messageHashes.append('orderbook:' + symbol)
        orderbook = await self.watch_public_multiple(messageHashes, topics, params)
        if incrementalFeed:
            return orderbook.limit()
        else:
            return orderbook

    def handle_order_book(self, client: Client, message):
        #
        #   {
        #       "action":"snapshot",
        #       "arg":{
        #          "instType":"SPOT",
        #          "channel":"books5",
        #          "instId":"BTCUSDT"
        #       },
        #       "data":[
        #          {
        #             "asks":[
        #                ["21041.11","0.0445"],
        #                ["21041.16","0.0411"],
        #                ["21041.21","0.0421"],
        #                ["21041.26","0.0811"],
        #                ["21041.65","1.9465"]
        #             ],
        #             "bids":[
        #                ["21040.76","0.0417"],
        #                ["21040.71","0.0434"],
        #                ["21040.66","0.1141"],
        #                ["21040.61","0.3004"],
        #                ["21040.60","1.3357"]
        #             ],
        #             "checksum": -1367582038,
        #             "ts":"1656413855484"
        #          }
        #       ]
        #   }
        #
        arg = self.safe_value(message, 'arg')
        channel = self.safe_string(arg, 'channel')
        instType = self.safe_string(arg, 'instType')
        marketType = 'spot' if (instType == 'SPOT') else 'contract'
        marketId = self.safe_string(arg, 'instId')
        market = self.safe_market(marketId, None, None, marketType)
        symbol = market['symbol']
        messageHash = 'orderbook:' + symbol
        data = self.safe_value(message, 'data')
        rawOrderBook = self.safe_value(data, 0)
        timestamp = self.safe_integer(rawOrderBook, 'ts')
        incrementalBook = channel == 'books'
        if incrementalBook:
            # storedOrderBook = self.safe_value(self.orderbooks, symbol)
            if not (symbol in self.orderbooks):
                # ob = self.order_book({})
                ob = self.counted_order_book({})
                ob['symbol'] = symbol
                self.orderbooks[symbol] = ob
            storedOrderBook = self.orderbooks[symbol]
            asks = self.safe_value(rawOrderBook, 'asks', [])
            bids = self.safe_value(rawOrderBook, 'bids', [])
            self.handle_deltas(storedOrderBook['asks'], asks)
            self.handle_deltas(storedOrderBook['bids'], bids)
            storedOrderBook['timestamp'] = timestamp
            storedOrderBook['datetime'] = self.iso8601(timestamp)
            checksum = self.safe_bool(self.options, 'checksum', True)
            isSnapshot = self.safe_string(message, 'action') == 'snapshot'  # snapshot does not have a checksum
            if not isSnapshot and checksum:
                storedAsks = storedOrderBook['asks']
                storedBids = storedOrderBook['bids']
                asksLength = len(storedAsks)
                bidsLength = len(storedBids)
                payloadArray = []
                for i in range(0, 25):
                    if i < bidsLength:
                        payloadArray.append(storedBids[i][2][0])
                        payloadArray.append(storedBids[i][2][1])
                    if i < asksLength:
                        payloadArray.append(storedAsks[i][2][0])
                        payloadArray.append(storedAsks[i][2][1])
                payload = ':'.join(payloadArray)
                calculatedChecksum = self.crc32(payload, True)
                responseChecksum = self.safe_integer(rawOrderBook, 'checksum')
                if calculatedChecksum != responseChecksum:
                    error = InvalidNonce(self.id + ' invalid checksum')
                    del client.subscriptions[messageHash]
                    del self.orderbooks[symbol]
                    client.reject(error, messageHash)
                    return
        else:
            orderbook = self.order_book({})
            parsedOrderbook = self.parse_order_book(rawOrderBook, symbol, timestamp)
            orderbook.reset(parsedOrderbook)
            self.orderbooks[symbol] = orderbook
        client.resolve(self.orderbooks[symbol], messageHash)

    def handle_delta(self, bookside, delta):
        bidAsk = self.parse_bid_ask(delta, 0, 1)
        # we store the string representations in the orderbook for checksum calculation
        # self simplifies the code for generating checksums do not need to do any complex number transformations
        bidAsk.append(delta)
        bookside.storeArray(bidAsk)

    def handle_deltas(self, bookside, deltas):
        for i in range(0, len(deltas)):
            self.handle_delta(bookside, deltas[i])

    async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :see: https://www.bitget.com/api-doc/spot/websocket/public/Trades-Channel
        :see: https://www.bitget.com/api-doc/contract/websocket/public/New-Trades-Channel
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        return await self.watch_trades_for_symbols([symbol], since, limit, params)

    async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :see: https://www.bitget.com/api-doc/spot/websocket/public/Trades-Channel
        :see: https://www.bitget.com/api-doc/contract/websocket/public/New-Trades-Channel
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        symbolsLength = len(symbols)
        if symbolsLength == 0:
            raise ArgumentsRequired(self.id + ' watchTradesForSymbols() requires a non-empty array of symbols')
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        topics = []
        messageHashes = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.market(symbol)
            instType = None
            instType, params = self.get_inst_type(market, params)
            args: dict = {
                'instType': instType,
                'channel': 'trade',
                'instId': market['id'],
            }
            topics.append(args)
            messageHashes.append('trade:' + symbol)
        trades = await self.watch_public_multiple(messageHashes, topics, params)
        if self.newUpdates:
            first = self.safe_value(trades, 0)
            tradeSymbol = self.safe_string(first, 'symbol')
            limit = trades.getLimit(tradeSymbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    def handle_trades(self, client: Client, message):
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {"instType": "SPOT", "channel": "trade", "instId": "BTCUSDT"},
        #         "data": [
        #             {
        #                 "ts": "1701910980366",
        #                 "price": "43854.01",
        #                 "size": "0.0535",
        #                 "side": "buy",
        #                 "tradeId": "1116461060594286593"
        #             },
        #         ],
        #         "ts": 1701910980730
        #     }
        #
        arg = self.safe_value(message, 'arg', {})
        instType = self.safe_string(arg, 'instType')
        marketType = 'spot' if (instType == 'SPOT') else 'contract'
        marketId = self.safe_string(arg, 'instId')
        market = self.safe_market(marketId, None, None, marketType)
        symbol = market['symbol']
        stored = self.safe_value(self.trades, symbol)
        if stored is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            stored = ArrayCache(limit)
            self.trades[symbol] = stored
        data = self.safe_list(message, 'data', [])
        length = len(data)
        # fix chronological order by reversing
        for i in range(0, length):
            index = length - i - 1
            rawTrade = data[index]
            parsed = self.parse_ws_trade(rawTrade, market)
            stored.append(parsed)
        messageHash = 'trade:' + symbol
        client.resolve(stored, messageHash)

    def parse_ws_trade(self, trade, market=None):
        #
        #     {
        #         "ts": "1701910980366",
        #         "price": "43854.01",
        #         "size": "0.0535",
        #         "side": "buy",
        #         "tradeId": "1116461060594286593"
        #     }
        # swap private
        #
        #            {
        #               "orderId": "1169142761031114781",
        #               "tradeId": "1169142761312637004",
        #               "symbol": "LTCUSDT",
        #               "orderType": "market",
        #               "side": "buy",
        #               "price": "80.87",
        #               "baseVolume": "0.1",
        #               "quoteVolume": "8.087",
        #               "profit": "0",
        #               "tradeSide": "open",
        #               "posMode": "hedge_mode",
        #               "tradeScope": "taker",
        #               "feeDetail": [
        #                  {
        #                     "feeCoin": "USDT",
        #                     "deduction": "no",
        #                     "totalDeductionFee": "0",
        #                     "totalFee": "-0.0048522"
        #                  }
        #               ],
        #               "cTime": "1714471276596",
        #               "uTime": "1714471276596"
        #            }
        # spot private
        #        {
        #           "orderId": "1169142457356959747",
        #           "tradeId": "1169142457636958209",
        #           "symbol": "LTCUSDT",
        #           "orderType": "market",
        #           "side": "buy",
        #           "priceAvg": "81.069",
        #           "size": "0.074",
        #           "amount": "5.999106",
        #           "tradeScope": "taker",
        #           "feeDetail": [
        #              {
        #                 "feeCoin": "LTC",
        #                 "deduction": "no",
        #                 "totalDeductionFee": "0",
        #                 "totalFee": "0.000074"
        #              }
        #           ],
        #           "cTime": "1714471204194",
        #           "uTime": "1714471204194"
        #        }
        #
        instId = self.safe_string_2(trade, 'symbol', 'instId')
        posMode = self.safe_string(trade, 'posMode')
        defaultType = 'contract' if (posMode is not None) else 'spot'
        if market is None:
            market = self.safe_market(instId, None, None, defaultType)
        timestamp = self.safe_integer_n(trade, ['uTime', 'cTime', 'ts'])
        feeDetail = self.safe_list(trade, 'feeDetail', [])
        first = self.safe_dict(feeDetail, 0)
        fee = None
        if first is not None:
            feeCurrencyId = self.safe_string(first, 'feeCoin')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': Precise.string_abs(self.safe_string(first, 'totalFee')),
                'currency': feeCurrencyCode,
            }
        return self.safe_trade({
            'info': trade,
            'id': self.safe_string(trade, 'tradeId'),
            'order': self.safe_string(trade, 'orderId'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'type': self.safe_string(trade, 'orderType'),
            'side': self.safe_string(trade, 'side'),
            'takerOrMaker': self.safe_string(trade, 'tradeScope'),
            'price': self.safe_string_2(trade, 'priceAvg', 'price'),
            'amount': self.safe_string_2(trade, 'size', 'baseVolume'),
            'cost': self.safe_string_2(trade, 'amount', 'quoteVolume'),
            'fee': fee,
        }, market)

    async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
        """
        watch all open positions
        :see: https://www.bitget.com/api-doc/contract/websocket/private/Positions-Channel
        :param str[]|None symbols: list of unified market symbols
        :param dict params: extra parameters specific to the exchange API endpoint
        :param str [params.instType]: one of 'USDT-FUTURES', 'USDC-FUTURES', 'COIN-FUTURES', 'SUSDT-FUTURES', 'SUSDC-FUTURES' or 'SCOIN-FUTURES', default is 'USDT-FUTURES'
        :returns dict[]: a list of `position structure <https://docs.ccxt_zagrava.com/en/latest/manual.html#position-structure>`
        """
        await self.load_markets()
        market = None
        messageHash = ''
        subscriptionHash = 'positions'
        instType = 'USDT-FUTURES'
        symbols = self.market_symbols(symbols)
        if not self.is_empty(symbols):
            market = self.get_market_from_symbols(symbols)
            instType, params = self.get_inst_type(market, params)
        messageHash = instType + ':positions' + messageHash
        args: dict = {
            'instType': instType,
            'channel': 'positions',
            'instId': 'default',
        }
        newPositions = await self.watch_private(messageHash, subscriptionHash, args, params)
        if self.newUpdates:
            return newPositions
        return self.filter_by_symbols_since_limit(newPositions, symbols, since, limit, True)

    def handle_positions(self, client: Client, message):
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {
        #             "instType": "USDT-FUTURES",
        #             "channel": "positions",
        #             "instId": "default"
        #         },
        #         "data": [
        #             {
        #                 "posId": "926036334386778112",
        #                 "instId": "BTCUSDT",
        #                 "marginCoin": "USDT",
        #                 "marginSize": "2.19245",
        #                 "marginMode": "crossed",
        #                 "holdSide": "long",
        #                 "posMode": "hedge_mode",
        #                 "total": "0.001",
        #                 "available": "0.001",
        #                 "frozen": "0",
        #                 "openPriceAvg": "43849",
        #                 "leverage": 20,
        #                 "achievedProfits": "0",
        #                 "unrealizedPL": "-0.0032",
        #                 "unrealizedPLR": "-0.00145955438",
        #                 "liquidationPrice": "17629.684814834",
        #                 "keepMarginRate": "0.004",
        #                 "marginRate": "0.007634649185",
        #                 "cTime": "1652331666985",
        #                 "uTime": "1701913016923",
        #                 "autoMargin": "off"
        #             },
        #             ...
        #         ]
        #         "ts": 1701913043767
        #     }
        #
        arg = self.safe_value(message, 'arg', {})
        instType = self.safe_string(arg, 'instType', '')
        if self.positions is None:
            self.positions = {}
        if not (instType in self.positions):
            self.positions[instType] = ArrayCacheBySymbolBySide()
        cache = self.positions[instType]
        rawPositions = self.safe_value(message, 'data', [])
        dataLength = len(rawPositions)
        if dataLength == 0:
            return
        newPositions = []
        for i in range(0, len(rawPositions)):
            rawPosition = rawPositions[i]
            marketId = self.safe_string(rawPosition, 'instId')
            market = self.safe_market(marketId, None, None, 'contract')
            position = self.parse_ws_position(rawPosition, market)
            newPositions.append(position)
            cache.append(position)
        messageHashes = self.find_message_hashes(client, instType + ':positions::')
        for i in range(0, len(messageHashes)):
            messageHash = messageHashes[i]
            parts = messageHash.split('::')
            symbolsString = parts[1]
            symbols = symbolsString.split(',')
            positions = self.filter_by_array(newPositions, 'symbol', symbols, False)
            if not self.is_empty(positions):
                client.resolve(positions, messageHash)
        client.resolve(newPositions, instType + ':positions')

    def parse_ws_position(self, position, market=None):
        #
        #     {
        #         "posId": "926036334386778112",
        #         "instId": "BTCUSDT",
        #         "marginCoin": "USDT",
        #         "marginSize": "2.19245",
        #         "marginMode": "crossed",
        #         "holdSide": "long",
        #         "posMode": "hedge_mode",
        #         "total": "0.001",
        #         "available": "0.001",
        #         "frozen": "0",
        #         "openPriceAvg": "43849",
        #         "leverage": 20,
        #         "achievedProfits": "0",
        #         "unrealizedPL": "-0.0032",
        #         "unrealizedPLR": "-0.00145955438",
        #         "liquidationPrice": "17629.684814834",
        #         "keepMarginRate": "0.004",
        #         "marginRate": "0.007634649185",
        #         "cTime": "1652331666985",
        #         "uTime": "1701913016923",
        #         "autoMargin": "off"
        #     }
        #
        marketId = self.safe_string(position, 'instId')
        marginModeId = self.safe_string(position, 'marginMode')
        marginMode = self.get_supported_mapping(marginModeId, {
            'crossed': 'cross',
            'isolated': 'isolated',
        })
        hedgedId = self.safe_string(position, 'posMode')
        hedged = True if (hedgedId == 'hedge_mode') else False
        timestamp = self.safe_integer_2(position, 'uTime', 'cTime')
        percentageDecimal = self.safe_string(position, 'unrealizedPLR')
        percentage = Precise.string_mul(percentageDecimal, '100')
        contractSize = None
        if market is not None:
            contractSize = market['contractSize']
        return self.safe_position({
            'info': position,
            'id': self.safe_string(position, 'posId'),
            'symbol': self.safe_symbol(marketId, market, None, 'contract'),
            'notional': None,
            'marginMode': marginMode,
            'liquidationPrice': self.safe_number(position, 'liquidationPrice'),
            'entryPrice': self.safe_number(position, 'openPriceAvg'),
            'unrealizedPnl': self.safe_number(position, 'unrealizedPL'),
            'percentage': self.parse_number(percentage),
            'contracts': self.safe_number(position, 'total'),
            'contractSize': contractSize,
            'markPrice': None,
            'side': self.safe_string(position, 'holdSide'),
            'hedged': hedged,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'maintenanceMargin': None,
            'maintenanceMarginPercentage': self.safe_number(position, 'keepMarginRate'),
            'collateral': None,
            'initialMargin': None,
            'initialMarginPercentage': None,
            'leverage': self.safe_number(position, 'leverage'),
            'marginRatio': self.safe_number(position, 'marginRate'),
        })

    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
        :see: https://www.bitget.com/api-doc/spot/websocket/private/Order-Channel
        :see: https://www.bitget.com/api-doc/contract/websocket/private/Order-Channel
        :see: https://www.bitget.com/api-doc/contract/websocket/private/Plan-Order-Channel
        :see: https://www.bitget.com/api-doc/margin/cross/websocket/private/Cross-Orders
        :see: https://www.bitget.com/api-doc/margin/isolated/websocket/private/Isolate-Orders
        :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 boolean [params.stop]: *contract only* set to True for watching trigger orders
        :param str [params.marginMode]: 'isolated' or 'cross' for watching spot margin orders]
        :param str [params.type]: 'spot', 'swap'
        :param str [params.subType]: 'linear', 'inverse'
        :returns dict[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = None
        marketId = None
        isTrigger = None
        isTrigger, params = self.is_trigger_order(params)
        messageHash = 'triggerOrder' if (isTrigger) else 'order'
        subscriptionHash = 'order:trades'
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            marketId = market['id']
            messageHash = messageHash + ':' + symbol
        productType = self.safe_string(params, 'productType')
        type = None
        type, params = self.handle_market_type_and_params('watchOrders', market, params)
        subType = None
        subType, params = self.handle_sub_type_and_params('watchOrders', market, params, 'linear')
        if (type == 'spot' or type == 'margin') and (symbol is None):
            raise ArgumentsRequired(self.id + ' watchOrders requires a symbol argument for ' + type + ' markets.')
        if (productType is None) and (type != 'spot') and (symbol is None):
            messageHash = messageHash + ':' + subType
        elif productType == 'USDT-FUTURES':
            messageHash = messageHash + ':linear'
        elif productType == 'COIN-FUTURES':
            messageHash = messageHash + ':inverse'
        elif productType == 'USDC-FUTURES':
            messageHash = messageHash + ':usdcfutures'  # non unified channel
        instType = None
        instType, params = self.get_inst_type(market, params)
        if type == 'spot':
            subscriptionHash = subscriptionHash + ':' + symbol
        if isTrigger:
            subscriptionHash = subscriptionHash + ':stop'  # we don't want to re-use the same subscription hash for stop orders
        instId = marketId if (type == 'spot' or type == 'margin') else 'default'  # different from other streams here the 'rest' id is required for spot markets, contract markets require default here
        channel = 'orders-algo' if isTrigger else 'orders'
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('watchOrders', params)
        if marginMode is not None:
            instType = 'MARGIN'
            messageHash = messageHash + ':' + marginMode
            if marginMode == 'isolated':
                channel = 'orders-isolated'
            else:
                channel = 'orders-crossed'
        subscriptionHash = subscriptionHash + ':' + instType
        args: dict = {
            'instType': instType,
            'channel': channel,
            'instId': instId,
        }
        orders = await self.watch_private(messageHash, subscriptionHash, args, params)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)

    def handle_order(self, client: Client, message):
        #
        # spot
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {"instType": "SPOT", "channel": "orders", "instId": "BTCUSDT"},
        #         "data": [
        #             # see all examples in parseWsOrder
        #         ],
        #         "ts": 1701923297285
        #     }
        #
        # contract
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {"instType": "USDT-FUTURES", "channel": "orders", "instId": "default"},
        #         "data": [
        #             # see all examples in parseWsOrder
        #         ],
        #         "ts": 1701920595879
        #     }
        #
        # isolated and cross margin
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {"instType": "MARGIN", "channel": "orders-crossed", "instId": "BTCUSDT"},
        #         "data": [
        #             # see examples in parseWsOrder
        #         ],
        #         "ts": 1701923982497
        #     }
        #
        arg = self.safe_dict(message, 'arg', {})
        channel = self.safe_string(arg, 'channel')
        instType = self.safe_string(arg, 'instType')
        argInstId = self.safe_string(arg, 'instId')
        marketType = None
        if instType == 'SPOT':
            marketType = 'spot'
        elif instType == 'MARGIN':
            marketType = 'spot'
        else:
            marketType = 'contract'
        isLinearSwap = (instType == 'USDT-FUTURES')
        isInverseSwap = (instType == 'COIN-FUTURES')
        isUSDCFutures = (instType == 'USDC-FUTURES')
        data = self.safe_value(message, 'data', [])
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
            self.triggerOrders = ArrayCacheBySymbolById(limit)
        isTrigger = (channel == 'orders-algo') or (channel == 'ordersAlgo')
        stored = self.triggerOrders if isTrigger else self.orders
        messageHash = 'triggerOrder' if isTrigger else 'order'
        marketSymbols: dict = {}
        for i in range(0, len(data)):
            order = data[i]
            marketId = self.safe_string(order, 'instId', argInstId)
            market = self.safe_market(marketId, None, None, marketType)
            parsed = self.parse_ws_order(order, market)
            stored.append(parsed)
            symbol = parsed['symbol']
            marketSymbols[symbol] = True
        keys = list(marketSymbols.keys())
        for i in range(0, len(keys)):
            symbol = keys[i]
            innerMessageHash = messageHash + ':' + symbol
            if channel == 'orders-crossed':
                innerMessageHash = innerMessageHash + ':cross'
            elif channel == 'orders-isolated':
                innerMessageHash = innerMessageHash + ':isolated'
            client.resolve(stored, innerMessageHash)
        client.resolve(stored, messageHash)
        if isLinearSwap:
            client.resolve(stored, 'order:linear')
        if isInverseSwap:
            client.resolve(stored, 'order:inverse')
        if isUSDCFutures:
            client.resolve(stored, 'order:usdcfutures')

    def parse_ws_order(self, order, market=None):
        #
        # spot
        #
        #   {
        #         instId: 'EOSUSDT',
        #         orderId: '1171779081105780739',
        #         price: '0.81075',  # limit price, field not present for market orders
        #         clientOid: 'a2330139-1d04-4d78-98be-07de3cfd1055',
        #         notional: '5.675250',  # self is not cost! but notional
        #         newSize: '7.0000',  # self is not cost! quanity(for limit order or market sell) or cost(for market buy order)
        #         size: '5.6752',  # self is not cost, neither quanity, but notional! self field for "spot" can be ignored at all
        #         # Note: for limit order(even filled) we don't have cost value in response, only in market order
        #         orderType: 'limit',  # limit, market
        #         force: 'gtc',
        #         side: 'buy',
        #         accBaseVolume: '0.0000',  # in case of 'filled', self would be set(for limit orders, self is the only indicator of the amount filled)
        #         priceAvg: '0.00000',  # in case of 'filled', self would be set
        #         status: 'live',  # live, filled, partially_filled
        #         cTime: '1715099824215',
        #         uTime: '1715099824215',
        #         feeDetail: [],
        #         enterPointSource: 'API'
        #                   #### trigger order has these additional fields:  ####
        #         "triggerPrice": "35100",
        #         "price": "35100",  # self is same price
        #         "executePrice": "35123",  # self is limit price
        #         "triggerType": "fill_price",
        #         "planType": "amount",
        #                   #### in case order had a partial fill:  ####
        #         fillPrice: '35123',
        #         tradeId: '1171775539946528779',
        #         baseVolume: '7',  # field present in market order
        #         fillTime: '1715098979937',
        #         fillFee: '-0.0069987',
        #         fillFeeCoin: 'BTC',
        #         tradeScope: 'T',
        #    }
        #
        # contract
        #
        #     {
        #         accBaseVolume: '0',  # total amount filled during lifetime for order
        #         cTime: '1715065875539',
        #         clientOid: '1171636690041344003',
        #         enterPointSource: 'API',
        #         feeDetail: [{
        #             "feeCoin": "USDT",
        #             "fee": "-0.162003"
        #         }],
        #         force: 'gtc',
        #         instId: 'SEOSSUSDT',
        #         leverage: '10',
        #         marginCoin: 'USDT',
        #         marginMode: 'crossed',
        #         notionalUsd: '10.4468',
        #         orderId: '1171636690028761089',
        #         orderType: 'market',
        #         posMode: 'hedge_mode',  # one_way_mode, hedge_mode
        #         posSide: 'short',  # short, long, net
        #         price: '0',  # zero for market order
        #         reduceOnly: 'no',
        #         side: 'sell',
        #         size: '13',  # self is contracts amount
        #         status: 'live',  # live, filled, cancelled
        #         tradeSide: 'open',
        #         uTime: '1715065875539'
        #                   #### when filled order is incoming, these additional fields are present too:  ###
        #         baseVolume: '9',  # amount filled for the incoming update/trade
        #         accBaseVolume: '13',  # i.e. 9 has been filled from 13 amount(self value is same as 'size')
        #         fillFee: '-0.0062712',
        #         fillFeeCoin: 'SUSDT',
        #         fillNotionalUsd: '10.452',
        #         fillPrice: '0.804',
        #         fillTime: '1715065875605',
        #         pnl: '0',
        #         priceAvg: '0.804',
        #         tradeId: '1171636690314407937',
        #         tradeScope: 'T',
        #                   #### trigger order has these additional fields:
        #         "triggerPrice": "0.800000000",
        #         "price": "0.800000000",  # <-- self is same price, actual limit-price is not present in initial response
        #         "triggerType": "mark_price",
        #         "triggerTime": "1715082796679",
        #         "planType": "pl",
        #         "actualSize": "0.000000000",
        #         "stopSurplusTriggerType": "fill_price",
        #         "stopLossTriggerType": "fill_price",
        #     }
        #
        # isolated and cross margin
        #
        #     {
        #         enterPointSource: "web",
        #         feeDetail: [
        #           {
        #             feeCoin: "AAVE",
        #             deduction: "no",
        #             totalDeductionFee: "0",
        #             totalFee: "-0.00010740",
        #           },
        #         ],
        #         force: "gtc",
        #         orderType: "limit",
        #         price: "93.170000000",
        #         fillPrice: "93.170000000",
        #         baseSize: "0.110600000",  # total amount of order
        #         quoteSize: "10.304602000",  # total cost of order(independently if order is filled or pending)
        #         baseVolume: "0.107400000",  # filled amount of order(during order's lifecycle, and not for self specific incoming update)
        #         fillTotalAmount: "10.006458000",  # filled cost of order(during order's lifecycle, and not for self specific incoming update)
        #         side: "buy",
        #         status: "partially_filled",
        #         cTime: "1717875017306",
        #         clientOid: "b57afe789a06454e9c560a2aab7f7201",
        #         loanType: "auto-loan",
        #         orderId: "1183419084588060673",
        #       }
        #
        isSpot = not ('posMode' in order)
        isMargin = ('loanType' in order)
        marketId = self.safe_string(order, 'instId')
        market = self.safe_market(marketId, market)
        timestamp = self.safe_integer(order, 'cTime')
        symbol = market['symbol']
        rawStatus = self.safe_string(order, 'status')
        orderFee = self.safe_value(order, 'feeDetail', [])
        fee = self.safe_value(orderFee, 0)
        feeAmount = self.safe_string(fee, 'fee')
        feeObject = None
        if feeAmount is not None:
            feeCurrency = self.safe_string(fee, 'feeCoin')
            feeObject = {
                'cost': self.parse_number(Precise.string_abs(feeAmount)),
                'currency': self.safe_currency_code(feeCurrency),
            }
        triggerPrice = self.safe_number(order, 'triggerPrice')
        isTriggerOrder = (triggerPrice is not None)
        price = None
        if not isTriggerOrder:
            price = self.safe_number(order, 'price')
        elif isSpot and isTriggerOrder:
            # for spot trigger order, limit price is self
            price = self.safe_number(order, 'executePrice')
        avgPrice = self.omit_zero(self.safe_string_2(order, 'priceAvg', 'fillPrice'))
        side = self.safe_string(order, 'side')
        type = self.safe_string(order, 'orderType')
        accBaseVolume = self.omit_zero(self.safe_string(order, 'accBaseVolume'))
        newSizeValue = self.omit_zero(self.safe_string(order, 'newSize'))
        isMarketOrder = (type == 'market')
        isBuy = (side == 'buy')
        totalAmount = None
        filledAmount = None
        cost = None
        remaining = None
        totalFilled = self.safe_string(order, 'accBaseVolume')
        if isSpot:
            if isMargin:
                totalAmount = self.safe_string(order, 'baseSize')
                totalFilled = self.safe_string(order, 'baseVolume')
                cost = self.safe_string(order, 'fillTotalAmount')
            else:
                partialFillAmount = self.safe_string(order, 'baseVolume')
                if partialFillAmount is not None:
                    filledAmount = partialFillAmount
                else:
                    filledAmount = totalFilled
                if isMarketOrder:
                    if isBuy:
                        totalAmount = accBaseVolume
                        cost = newSizeValue
                    else:
                        totalAmount = newSizeValue
                        # we don't have cost for market-sell order
                else:
                    totalAmount = self.safe_string(order, 'newSize')
                    # we don't have cost for limit order
        else:
            # baseVolume should not be used for "amount" for contracts !
            filledAmount = self.safe_string(order, 'baseVolume')
            totalAmount = self.safe_string(order, 'size')
            cost = self.safe_string(order, 'fillNotionalUsd')
        remaining = Precise.string_sub(totalAmount, totalFilled)
        return self.safe_order({
            'info': order,
            'symbol': symbol,
            'id': self.safe_string(order, 'orderId'),
            'clientOrderId': self.safe_string(order, 'clientOid'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': self.safe_integer(order, 'uTime'),
            'type': type,
            'timeInForce': self.safe_string_upper(order, 'force'),
            'postOnly': None,
            'side': side,
            'price': price,
            'triggerPrice': triggerPrice,
            'amount': totalAmount,
            'cost': cost,
            'average': avgPrice,
            'filled': filledAmount,
            'remaining': remaining,
            'status': self.parse_ws_order_status(rawStatus),
            'fee': feeObject,
            'trades': None,
        }, market)

    def parse_ws_order_status(self, status):
        statuses: dict = {
            'live': 'open',
            'partially_filled': 'open',
            'filled': 'closed',
            'cancelled': 'canceled',
            'not_trigger': 'open',
        }
        return self.safe_string(statuses, status, status)

    async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        watches trades made by the user
        :see: https://www.bitget.com/api-doc/contract/websocket/private/Order-Channel
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=trade-structure>`
        """
        await self.load_markets()
        market = None
        messageHash = 'myTrades'
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash = messageHash + ':' + symbol
        instType = None
        instType, params = self.get_inst_type(market, params)
        subscriptionHash = 'fill:' + instType
        args: dict = {
            'instType': instType,
            'channel': 'fill',
            'instId': 'default',
        }
        trades = await self.watch_private(messageHash, subscriptionHash, args, params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)

    def handle_my_trades(self, client: Client, message):
        #
        # spot
        # {
        #     "action": "snapshot",
        #     "arg": {
        #        "instType": "SPOT",
        #        "channel": "fill",
        #        "instId": "default"
        #     },
        #     "data": [
        #        {
        #           "orderId": "1169142457356959747",
        #           "tradeId": "1169142457636958209",
        #           "symbol": "LTCUSDT",
        #           "orderType": "market",
        #           "side": "buy",
        #           "priceAvg": "81.069",
        #           "size": "0.074",
        #           "amount": "5.999106",
        #           "tradeScope": "taker",
        #           "feeDetail": [
        #              {
        #                 "feeCoin": "LTC",
        #                 "deduction": "no",
        #                 "totalDeductionFee": "0",
        #                 "totalFee": "0.000074"
        #              }
        #           ],
        #           "cTime": "1714471204194",
        #           "uTime": "1714471204194"
        #        }
        #     ],
        #     "ts": 1714471204270
        # }
        # swap
        #     {
        #         "action": "snapshot",
        #         "arg": {
        #            "instType": "USDT-FUTURES",
        #            "channel": "fill",
        #            "instId": "default"
        #         },
        #         "data": [
        #            {
        #               "orderId": "1169142761031114781",
        #               "tradeId": "1169142761312637004",
        #               "symbol": "LTCUSDT",
        #               "orderType": "market",
        #               "side": "buy",
        #               "price": "80.87",
        #               "baseVolume": "0.1",
        #               "quoteVolume": "8.087",
        #               "profit": "0",
        #               "tradeSide": "open",
        #               "posMode": "hedge_mode",
        #               "tradeScope": "taker",
        #               "feeDetail": [
        #                  {
        #                     "feeCoin": "USDT",
        #                     "deduction": "no",
        #                     "totalDeductionFee": "0",
        #                     "totalFee": "-0.0048522"
        #                  }
        #               ],
        #               "cTime": "1714471276596",
        #               "uTime": "1714471276596"
        #            }
        #         ],
        #         "ts": 1714471276629
        #     }
        #
        if self.myTrades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            self.myTrades = ArrayCache(limit)
        stored = self.myTrades
        data = self.safe_list(message, 'data', [])
        length = len(data)
        messageHash = 'myTrades'
        for i in range(0, length):
            trade = data[i]
            parsed = self.parse_ws_trade(trade)
            stored.append(parsed)
            symbol = parsed['symbol']
            symbolSpecificMessageHash = 'myTrades:' + symbol
            client.resolve(stored, symbolSpecificMessageHash)
        client.resolve(stored, messageHash)

    async def watch_balance(self, params={}) -> Balances:
        """
        watch balance and get the amount of funds available for trading or funds locked in orders
        :see: https://www.bitget.com/api-doc/spot/websocket/private/Account-Channel
        :see: https://www.bitget.com/api-doc/contract/websocket/private/Account-Channel
        :see: https://www.bitget.com/api-doc/margin/cross/websocket/private/Margin-Cross-Account-Assets
        :see: https://www.bitget.com/api-doc/margin/isolated/websocket/private/Margin-isolated-account-assets
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.type]: spot or contract if not provided self.options['defaultType'] is used
        :param str [params.instType]: one of 'SPOT', 'MARGIN', 'USDT-FUTURES', 'USDC-FUTURES', 'COIN-FUTURES', 'SUSDT-FUTURES', 'SUSDC-FUTURES' or 'SCOIN-FUTURES'
        :param str [params.marginMode]: 'isolated' or 'cross' for watching spot margin balances
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        type = None
        type, params = self.handle_market_type_and_params('watchBalance', None, params)
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('watchBalance', params)
        instType = None
        channel = 'account'
        if (type == 'swap') or (type == 'future'):
            instType = 'USDT-FUTURES'
        elif marginMode is not None:
            instType = 'MARGIN'
            if marginMode == 'isolated':
                channel = 'account-isolated'
            else:
                channel = 'account-crossed'
        else:
            instType = 'SPOT'
        instType, params = self.handle_option_and_params(params, 'watchBalance', 'instType', instType)
        args: dict = {
            'instType': instType,
            'channel': channel,
            'coin': 'default',
        }
        messageHash = 'balance:' + instType.lower()
        return await self.watch_private(messageHash, messageHash, args, params)

    def handle_balance(self, client: Client, message):
        #
        # spot
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {"instType": "SPOT", "channel": "account", "coin": "default"},
        #         "data": [
        #             {
        #                 "coin": "USDT",
        #                 "available": "19.1430952856087",
        #                 "frozen": "7",
        #                 "locked": "0",
        #                 "limitAvailable": "0",
        #                 "uTime": "1701931970487"
        #             },
        #         ],
        #         "ts": 1701931970487
        #     }
        #
        # swap
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {"instType": "USDT-FUTURES", "channel": "account", "coin": "default"},
        #         "data": [
        #             {
        #                 "marginCoin": "USDT",
        #                 "frozen": "5.36581500",
        #                 "available": "26.14309528",
        #                 "maxOpenPosAvailable": "20.77728028",
        #                 "maxTransferOut": "20.77728028",
        #                 "equity": "26.14309528",
        #                 "usdtEquity": "26.143095285166"
        #             }
        #         ],
        #         "ts": 1701932570822
        #     }
        #
        # margin
        #
        #     {
        #         "action": "snapshot",
        #         "arg": {"instType": "MARGIN", "channel": "account-crossed", "coin": "default"},
        #         "data": [
        #             {
        #                 "uTime": "1701933110544",
        #                 "id": "1096916799926710272",
        #                 "coin": "USDT",
        #                 "available": "16.24309528",
        #                 "borrow": "0.00000000",
        #                 "frozen": "9.90000000",
        #                 "interest": "0.00000000",
        #                 "coupon": "0.00000000"
        #             }
        #         ],
        #         "ts": 1701933110544
        #     }
        #
        data = self.safe_value(message, 'data', [])
        for i in range(0, len(data)):
            rawBalance = data[i]
            currencyId = self.safe_string_2(rawBalance, 'coin', 'marginCoin')
            code = self.safe_currency_code(currencyId)
            account = self.balance[code] if (code in self.balance) else self.account()
            borrow = self.safe_string(rawBalance, 'borrow')
            if borrow is not None:
                interest = self.safe_string(rawBalance, 'interest')
                account['debt'] = Precise.string_add(borrow, interest)
            freeQuery = 'maxTransferOut' if ('maxTransferOut' in rawBalance) else 'available'
            account['free'] = self.safe_string(rawBalance, freeQuery)
            account['total'] = self.safe_string(rawBalance, 'equity')
            account['used'] = self.safe_string(rawBalance, 'frozen')
            self.balance[code] = account
        self.balance = self.safe_balance(self.balance)
        arg = self.safe_value(message, 'arg')
        instType = self.safe_string_lower(arg, 'instType')
        messageHash = 'balance:' + instType
        client.resolve(self.balance, messageHash)

    async def watch_public(self, messageHash, args, params={}):
        url = self.urls['api']['ws']['public']
        request: dict = {
            'op': 'subscribe',
            'args': [args],
        }
        message = self.extend(request, params)
        return await self.watch(url, messageHash, message, messageHash)

    async def watch_public_multiple(self, messageHashes, argsArray, params={}):
        url = self.urls['api']['ws']['public']
        request: dict = {
            'op': 'subscribe',
            'args': argsArray,
        }
        message = self.extend(request, params)
        return await self.watch_multiple(url, messageHashes, message, messageHashes)

    async def authenticate(self, params={}):
        self.check_required_credentials()
        url = self.urls['api']['ws']['private']
        client = self.client(url)
        messageHash = 'authenticated'
        future = client.future(messageHash)
        authenticated = self.safe_value(client.subscriptions, messageHash)
        if authenticated is None:
            timestamp = str(self.seconds())
            auth = timestamp + 'GET' + '/user/verify'
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256, 'base64')
            operation = 'login'
            request: dict = {
                'op': operation,
                'args': [
                    {
                        'apiKey': self.apiKey,
                        'passphrase': self.password,
                        'timestamp': timestamp,
                        'sign': signature,
                    },
                ],
            }
            message = self.extend(request, params)
            self.watch(url, messageHash, message, messageHash)
        return await future

    async def watch_private(self, messageHash, subscriptionHash, args, params={}):
        await self.authenticate()
        url = self.urls['api']['ws']['private']
        request: dict = {
            'op': 'subscribe',
            'args': [args],
        }
        message = self.extend(request, params)
        return await self.watch(url, messageHash, message, subscriptionHash)

    def handle_authenticate(self, client: Client, message):
        #
        #  {event: "login", code: 0}
        #
        messageHash = 'authenticated'
        future = self.safe_value(client.futures, messageHash)
        future.resolve(True)

    def handle_error_message(self, client: Client, message):
        #
        #    {event: "error", code: 30015, msg: "Invalid sign"}
        #
        event = self.safe_string(message, 'event')
        try:
            if event == 'error':
                code = self.safe_string(message, 'code')
                feedback = self.id + ' ' + self.json(message)
                self.throw_exactly_matched_exception(self.exceptions['ws']['exact'], code, feedback)
                msg = self.safe_string(message, 'msg', '')
                self.throw_broadly_matched_exception(self.exceptions['ws']['broad'], msg, feedback)
                raise ExchangeError(feedback)
            return False
        except Exception as e:
            if isinstance(e, AuthenticationError):
                messageHash = 'authenticated'
                client.reject(e, messageHash)
                if messageHash in client.subscriptions:
                    del client.subscriptions[messageHash]
            else:
                # Note: if error happens on a subscribe event, user will have to close exchange to resubscribe. Issue  #19041
                client.reject(e)
            return True

    def handle_message(self, client: Client, message):
        #
        #   {
        #       "action": "snapshot",
        #       "arg": {instType: 'SPOT', channel: "ticker", instId: "BTCUSDT"},
        #       "data": [
        #         {
        #           "instId": "BTCUSDT",
        #           "last": "21150.53",
        #           "open24h": "20759.65",
        #           "high24h": "21202.29",
        #           "low24h": "20518.82",
        #           "bestBid": "21150.500000",
        #           "bestAsk": "21150.600000",
        #           "baseVolume": "25402.1961",
        #           "quoteVolume": "530452554.2156",
        #           "ts": 1656408934044,
        #           "labeId": 0
        #         }
        #       ]
        #   }
        # pong message
        #    "pong"
        #
        # login
        #
        #     {event: "login", code: 0}
        #
        # subscribe
        #
        #    {
        #        "event": "subscribe",
        #        "arg": {instType: 'SPOT', channel: "account", instId: "default"}
        #    }
        #
        if self.handle_error_message(client, message):
            return
        content = self.safe_string(message, 'message')
        if content == 'pong':
            self.handle_pong(client, message)
            return
        if message == 'pong':
            self.handle_pong(client, message)
            return
        event = self.safe_string(message, 'event')
        if event == 'login':
            self.handle_authenticate(client, message)
            return
        if event == 'subscribe':
            self.handle_subscription_status(client, message)
            return
        methods: dict = {
            'ticker': self.handle_ticker,
            'trade': self.handle_trades,
            'fill': self.handle_my_trades,
            'orders': self.handle_order,
            'ordersAlgo': self.handle_order,
            'orders-algo': self.handle_order,
            'orders-crossed': self.handle_order,
            'orders-isolated': self.handle_order,
            'account': self.handle_balance,
            'positions': self.handle_positions,
            'account-isolated': self.handle_balance,
            'account-crossed': self.handle_balance,
        }
        arg = self.safe_value(message, 'arg', {})
        topic = self.safe_value(arg, 'channel', '')
        method = self.safe_value(methods, topic)
        if method is not None:
            method(client, message)
        if topic.find('candle') >= 0:
            self.handle_ohlcv(client, message)
        if topic.find('books') >= 0:
            self.handle_order_book(client, message)

    def ping(self, client):
        return 'ping'

    def handle_pong(self, client: Client, message):
        client.lastPong = self.milliseconds()
        return message

    def handle_subscription_status(self, client: Client, message):
        #
        #    {
        #        "event": "subscribe",
        #        "arg": {instType: 'SPOT', channel: "account", instId: "default"}
        #    }
        #
        return message
