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

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

from ccxt.pro.base.exchange import Exchange
import ccxt.async_support
from ccxt.pro.base.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
import hashlib
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import NotSupported
from ccxt.base.errors import InvalidNonce
from ccxt.base.precise import Precise


class bitget(Exchange, ccxt.async_support.bitget):

    def describe(self):
        return self.deep_extend(super(bitget, self).describe(), {
            'has': {
                'ws': True,
                'watchBalance': True,
                'watchMyTrades': True,
                'watchOHLCV': True,
                'watchOrderBook': True,
                'watchOrders': True,
                'watchTicker': True,
                'watchTickers': False,
                'watchTrades': True,
            },
            'urls': {
                'api': {
                    'ws': 'wss://ws.bitget.com/spot/v1/stream',
                },
            },
            '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"}
                        '30015': AuthenticationError,  # {event: 'error', code: 30015, msg: 'Invalid sign'}
                    },
                },
            },
        })

    def get_ws_market_id(self, market):
        # WS don't use the same 'id'
        # as the rest version
        sandboxMode = self.safe_value(self.options, 'sandboxMode', False)
        if market['spot']:
            return market['info']['symbolName']
        else:
            if not sandboxMode:
                return market['id'].replace('_UMCBL', '')
            else:
                return market['id'].replace('_SUMCBL', '')

    def get_market_id_from_arg(self, arg):
        #
        # {arg: {instType: 'sp', channel: 'ticker', instId: 'BTCUSDT'}
        #
        instType = self.safe_string(arg, 'instType')
        sandboxMode = self.safe_value(self.options, 'sandboxMode', False)
        marketId = self.safe_string(arg, 'instId')
        if instType == 'sp':
            marketId += '_SPBL'
        else:
            if not sandboxMode:
                marketId += '_UMCBL'
            else:
                marketId += '_SUMCBL'
        return marketId

    async def watch_ticker(self, symbol, params={}):
        """
        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 bitget api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        messageHash = 'ticker:' + symbol
        instType = 'sp' if market['spot'] else 'mc'
        args = {
            'instType': instType,
            'channel': 'ticker',
            'instId': self.get_ws_market_id(market),
        }
        return await self.watch_public(messageHash, args, params)

    def handle_ticker(self, client, message):
        #
        #   {
        #       action: 'snapshot',
        #       arg: {instType: 'sp', 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
        #         }
        #       ]
        #   }
        #
        ticker = self.parse_ws_ticker(message)
        symbol = ticker['symbol']
        self.tickers[symbol] = ticker
        messageHash = 'ticker:' + symbol
        client.resolve(ticker, messageHash)
        return message

    def parse_ws_ticker(self, message, market=None):
        #
        # spot
        #     {
        #         action: 'snapshot',
        #         arg: {instType: 'sp', 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
        #           }
        #         ]
        #     }
        #
        # contract
        #
        #     {
        #         "action":"snapshot",
        #         "arg":{
        #            "instType":"mc",
        #            "channel":"ticker",
        #            "instId":"LTCUSDT"
        #         },
        #         "data":[
        #            {
        #               "instId":"LTCUSDT",
        #               "last":"52.77",
        #               "bestAsk":"52.78",
        #               "bestBid":"52.75",
        #               "high24h":"54.83",
        #               "low24h":"51.32",
        #               "priceChangePercent":"-0.02",
        #               "capitalRate":"-0.000100",
        #               "nextSettleTime":1656514800000,
        #               "systemTime":1656513146169,
        #               "markPrice":"52.77",
        #               "indexPrice":"52.80",
        #               "holding":"269813.9",
        #               "baseVolume":"75422.0",
        #               "quoteVolume":"3986579.8"
        #            }
        #         ]
        #     }
        #
        arg = self.safe_value(message, 'arg', {})
        data = self.safe_value(message, 'data', [])
        ticker = self.safe_value(data, 0, {})
        timestamp = self.safe_integer_2(ticker, 'ts', 'systemTime')
        marketId = self.get_market_id_from_arg(arg)
        market = self.safe_market(marketId, market)
        close = self.safe_string(ticker, 'last')
        open = self.safe_string(ticker, 'open24h')
        high = self.safe_string(ticker, 'high24h')
        low = self.safe_string(ticker, 'low24h')
        baseVolume = self.safe_string(ticker, 'baseVolume')
        quoteVolume = self.safe_string(ticker, 'quoteVolume')
        bid = self.safe_string(ticker, 'bestBid')
        ask = self.safe_string(ticker, 'bestAsk')
        return self.safe_ticker({
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': high,
            'low': low,
            'bid': bid,
            'bidVolume': None,
            'ask': ask,
            'askVolume': None,
            'vwap': None,
            'open': open,
            'close': close,
            'last': close,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    async def watch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        """
        watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the bitget api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, 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 = 'sp' if market['spot'] else 'mc'
        args = {
            'instType': instType,
            'channel': 'candle' + interval,
            'instId': self.get_ws_market_id(market),
        }
        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, message):
        #
        #   {
        #       "action":"snapshot",
        #       "arg":{
        #          "instType":"sp",
        #          "channel":"candle1W",
        #          "instId":"BTCUSDT"
        #       },
        #       "data":[
        #          [
        #             "1595779200000",
        #             "9960.05",
        #             "12099.95",
        #             "9839.7",
        #             "11088.68",
        #             "462484.9738"
        #          ],
        #          [
        #             "1596384000000",
        #             "11088.68",
        #             "11909.89",
        #             "10937.54",
        #             "11571.88",
        #             "547596.6484"
        #          ]
        #       ]
        #   }
        #
        arg = self.safe_value(message, 'arg', {})
        marketId = self.get_market_id_from_arg(arg)
        channel = self.safe_string(arg, 'channel')
        interval = channel.replace('candle', '')
        timeframes = self.safe_value(self.options, 'timeframes')
        timeframe = self.find_timeframe(interval, timeframes)
        market = self.safe_market(marketId)
        symbol = market['symbol']
        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
        data = self.safe_value(message, 'data', [])
        for i in range(0, len(data)):
            parsed = self.parse_ws_ohlcv(data[i])
            stored.append(parsed)
        messageHash = 'candles:' + timeframe + ':' + symbol
        client.resolve(stored, messageHash)

    def parse_ws_ohlcv(self, ohlcv, market=None):
        #
        #   [
        #      "1595779200000",  # timestamp
        #      "9960.05",  # open
        #      "12099.95",  # high
        #      "9839.7",  # low
        #      "11088.68",  # close
        #      "462484.9738"  # volume
        #   ]
        #
        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, 5),
        ]

    async def watch_order_book(self, symbol, limit=None, params={}):
        """
        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|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the bitget api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        messageHash = 'orderbook' + ':' + symbol
        instType = 'sp' if market['spot'] else 'mc'
        channel = 'books'
        incrementalFeed = True
        if (limit == 5) or (limit == 15):
            channel += str(limit)
            incrementalFeed = False
        args = {
            'instType': instType,
            'channel': channel,
            'instId': self.get_ws_market_id(market),
        }
        orderbook = await self.watch_public(messageHash, args, params)
        if incrementalFeed:
            return orderbook.limit()
        else:
            return orderbook

    def handle_order_book(self, client, message):
        #
        #   {
        #       "action":"snapshot",
        #       "arg":{
        #          "instType":"sp",
        #          "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"]
        #             ],
        #             "ts":"1656413855484"
        #          }
        #       ]
        #   }
        #
        arg = self.safe_value(message, 'arg')
        channel = self.safe_string(arg, 'channel')
        marketId = self.get_market_id_from_arg(arg)
        market = self.safe_market(marketId)
        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'
        storedOrderBook = None
        if incrementalBook:
            storedOrderBook = self.safe_value(self.orderbooks, symbol)
            if storedOrderBook is None:
                storedOrderBook = self.counted_order_book({})
            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_value(self.options, 'checksum', True)
            if 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')
                    client.reject(error, messageHash)
        else:
            storedOrderBook = self.parse_order_book(rawOrderBook, symbol, timestamp)
        self.orderbooks[symbol] = storedOrderBook
        client.resolve(storedOrderBook, 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 as we 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, since=None, limit=None, params={}):
        """
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the bitget api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        messageHash = 'trade:' + symbol
        instType = 'sp' if market['spot'] else 'mc'
        args = {
            'instType': instType,
            'channel': 'trade',
            'instId': self.get_ws_market_id(market),
        }
        trades = await self.watch_public(messageHash, args, params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    def handle_trades(self, client, message):
        #
        #    {
        #        action: 'snapshot',
        #        arg: {instType: 'sp', channel: 'trade', instId: 'BTCUSDT'},
        #        data: [
        #          ['1656411148032', '21047.78', '2.2294', 'buy'],
        #          ['1656411142030', '21047.85', '2.1225', 'buy'],
        #          ['1656411133064', '21045.88', '1.7704', 'sell'],
        #          ['1656411126037', '21052.39', '2.6905', 'buy'],
        #          ['1656411118029', '21056.87', '1.2308', 'sell'],
        #          ['1656411108028', '21060.01', '1.7186', 'sell'],
        #          ['1656411100027', '21060.4', '1.3641', 'buy'],
        #          ['1656411093030', '21058.76', '1.5049', 'sell']
        #        ]
        #    }
        #
        arg = self.safe_value(message, 'arg', {})
        marketId = self.get_market_id_from_arg(arg)
        market = self.safe_market(marketId)
        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_value(message, 'data', [])
        for j in range(0, len(data)):
            rawTrade = data[j]
            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):
        #
        # public trade
        #
        #   [
        #       '1656411148032',  # timestamp
        #       '21047.78',  # price
        #       '2.2294',  # size
        #       'buy',  # side
        #   ]
        #
        market = self.safe_market(None, market)
        timestamp = self.safe_integer(trade, 0)
        side = self.safe_string(trade, 3)
        price = self.safe_string(trade, 1)
        amount = self.safe_string(trade, 2)
        return self.safe_trade({
            'info': trade,
            'id': None,
            'order': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'type': None,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': None,
            'fee': None,
        }, market)

    async def watch_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        watches information on multiple orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the bitget api endpoint
        :returns [dict]: a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure
        """
        await self.load_markets()
        market = None
        marketId = None
        messageHash = 'order'
        subscriptionHash = 'order:trades'
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            marketId = market['id']
            messageHash = messageHash + ':' + symbol
        type = None
        type, params = self.handle_market_type_and_params('watchOrders', market, params)
        if (type == 'spot') and (symbol is None):
            raise ArgumentsRequired(self.id + ' watchOrders requires a symbol argument for ' + type + ' markets.')
        sandboxMode = self.safe_value(self.options, 'sandboxMode', False)
        instType = None
        if type == 'spot':
            instType = 'spbl'
        else:
            if not sandboxMode:
                instType = 'UMCBL'
            else:
                instType = 'SUMCBL'
        instId = marketId if (type == 'spot') else 'default'  # different from other streams here the 'rest' id is required for spot markets, contract markets require default here
        args = {
            'instType': instType,
            'channel': 'orders',
            '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, message, subscription=None):
        #
        #
        # spot order
        #    {
        #        action: 'snapshot',
        #        arg: {instType: 'spbl', channel: 'orders', instId: 'LTCUSDT_SPBL'  # instId='default' for contracts},
        #        data: [
        #          {
        #            instId: 'LTCUSDT_SPBL',
        #            ordId: '925999649898545152',
        #            clOrdId: '8b2aa69a-6a09-46c0-a50d-7ed50277394c',
        #            px: '20.00',
        #            sz: '0.3000',
        #            notional: '6.000000',
        #            ordType: 'limit',
        #            force: 'normal',
        #            side: 'buy',
        #            accFillSz: '0.0000',
        #            avgPx: '0.00',
        #            status: 'new',
        #            cTime: 1656501441454,
        #            uTime: 1656501441454,
        #            orderFee: []
        #          }
        #        ]
        #    }
        #
        arg = self.safe_value(message, 'arg', {})
        instType = self.safe_string(arg, 'instType')
        sandboxMode = self.safe_value(self.options, 'sandboxMode', False)
        isContractUpdate = (instType == 'umcbl') if (not sandboxMode) else (instType == 'sumcbl')
        data = self.safe_value(message, 'data', [])
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        stored = self.orders
        marketSymbols = {}
        for i in range(0, len(data)):
            order = data[i]
            execType = self.safe_string(order, 'execType')
            if (execType == 'T') and isContractUpdate:
                # partial order updates have the trade info inside
                self.handle_my_trades(client, order)
            parsed = self.parse_ws_order(order)
            stored.append(parsed)
            symbol = parsed['symbol']
            marketSymbols[symbol] = True
        keys = list(marketSymbols.keys())
        for i in range(0, len(keys)):
            symbol = keys[i]
            messageHash = 'order:' + symbol
            client.resolve(stored, messageHash)
        client.resolve(stored, 'order')

    def parse_ws_order(self, order, market=None):
        #
        # spot order
        #     {
        #         instId: 'LTCUSDT_SPBL',
        #         ordId: '925999649898545152',
        #         clOrdId: '8b2aa69a-6a09-46c0-a50d-7ed50277394c',
        #         px: '20.00',
        #         sz: '0.3000',
        #         notional: '6.000000',
        #         ordType: 'limit',
        #         force: 'normal',
        #         side: 'buy',
        #         accFillSz: '0.0000',
        #         avgPx: '0.00',
        #         status: 'new',
        #         cTime: 1656501441454,
        #         uTime: 1656501441454,
        #         orderFee: []
        #     }
        # partial fill
        #
        #    {
        #        instId: 'LTCUSDT_SPBL',
        #        ordId: '926006174213914625',
        #        clOrdId: '7ce28714-0016-46d0-a971-9a713a9923c5',
        #        notional: '5.000000',
        #        ordType: 'market',
        #        force: 'normal',
        #        side: 'buy',
        #        fillPx: '52.11',
        #        tradeId: '926006174514073601',
        #        fillSz: '0.0959',
        #        fillTime: '1656502997043',
        #        fillFee: '-0.0000959',
        #        fillFeeCcy: 'LTC',
        #        execType: 'T',
        #        accFillSz: '0.0959',
        #        avgPx: '52.11',
        #        status: 'partial-fill',
        #        cTime: 1656502996972,
        #        uTime: 1656502997119,
        #        orderFee: [Array]
        #    }
        #
        # contract order
        #    {
        #        accFillSz: '0',
        #        cTime: 1656510642518,
        #        clOrdId: '926038241960431617',
        #        force: 'normal',
        #        instId: 'LTCUSDT_UMCBL',
        #        lever: '20',
        #        notionalUsd: '7.5',
        #        ordId: '926038241859768320',
        #        ordType: 'limit',
        #        orderFee: [
        #             {feeCcy: 'USDT', fee: '0'}
        #        ]
        #        posSide: 'long',
        #        px: '25',
        #        side: 'buy',
        #        status: 'new',
        #        sz: '0.3',
        #        tdMode: 'cross',
        #        tgtCcy: 'USDT',
        #        uTime: 1656510642518
        #    }
        #
        marketId = self.safe_string(order, 'instId')
        market = self.safe_market(marketId, market)
        id = self.safe_string(order, 'ordId')
        clientOrderId = self.safe_string(order, 'clOrdId')
        price = self.safe_string(order, 'px')
        filled = self.safe_string(order, 'fillSz')
        amount = self.safe_string(order, 'sz')
        cost = self.safe_string_2(order, 'notional', 'notionalUsd')
        average = self.omit_zero(self.safe_string(order, 'avgPx'))
        type = self.safe_string(order, 'ordType')
        timestamp = self.safe_integer(order, 'cTime')
        symbol = market['symbol']
        side = self.safe_string_2(order, 'side', 'posSide')
        if (side == 'open_long') or (side == 'close_short'):
            side = 'buy'
        elif (side == 'close_long') or (side == 'open_short'):
            side = 'sell'
        rawStatus = self.safe_string(order, 'status', 'state')
        timeInForce = self.safe_string(order, 'force')
        status = self.parse_ws_order_status(rawStatus)
        orderFee = self.safe_value(order, 'orderFee', [])
        fee = self.safe_value(orderFee, 0)
        feeAmount = self.safe_string(fee, 'fee')
        feeObject = None
        if feeAmount is not None:
            feeCurrency = self.safe_string(fee, 'feeCcy')
            feeObject = {
                'cost': Precise.string_abs(feeAmount),
                'currency': self.safe_currency_code(feeCurrency),
            }
        return self.safe_order({
            'info': order,
            'symbol': symbol,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'triggerPrice': None,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': feeObject,
            'trades': None,
        }, market)

    def parse_ws_order_status(self, status):
        statuses = {
            'new': 'open',
            'partial-fill': 'open',
            'full-fill': 'closed',
            'filled': 'closed',
            'cancelled': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    async def watch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        watches trades made by the user
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades structures to retrieve
        :param dict params: extra parameters specific to the bitget api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        # only contracts stream provides the trade info consistently in between order updates
        # the spot stream only provides on limit orders updates so we can't support it for spot
        await self.load_markets()
        market = None
        messageHash = 'myTrades'
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash = messageHash + ':' + symbol
        type = None
        type, params = self.handle_market_type_and_params('watchMyTrades', market, params)
        if type == 'spot':
            raise NotSupported(self.id + ' watchMyTrades is not supported for ' + type + ' markets.')
        sandboxMode = self.safe_value(self.options, 'sandboxMode', False)
        subscriptionHash = 'order:trades'
        args = {
            'instType': 'umcbl' if (not sandboxMode) else 'sumcbl',
            'channel': 'orders',
            '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, message):
        #
        # order and trade mixin(contract)
        #
        #   {
        #       accFillSz: '0.1',
        #       avgPx: '52.81',
        #       cTime: 1656511777208,
        #       clOrdId: '926043001195237376',
        #       execType: 'T',
        #       fillFee: '-0.0031686',
        #       fillFeeCcy: 'USDT',
        #       fillNotionalUsd: '5.281',
        #       fillPx: '52.81',
        #       fillSz: '0.1',
        #       fillTime: '1656511777266',
        #       force: 'normal',
        #       instId: 'LTCUSDT_UMCBL',
        #       lever: '1',
        #       notionalUsd: '5.281',
        #       ordId: '926043001132322816',
        #       ordType: 'market',
        #       orderFee: [Array],
        #       pnl: '0.004',
        #       posSide: 'long',
        #       px: '0',
        #       side: 'sell',
        #       status: 'full-fill',
        #       sz: '0.1',
        #       tdMode: 'cross',
        #       tgtCcy: 'USDT',
        #       tradeId: '926043001438552105',
        #       uTime: 1656511777266
        #   }
        #
        if self.myTrades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            self.myTrades = ArrayCache(limit)
        stored = self.myTrades
        parsed = self.parse_ws_my_trade(message)
        stored.append(parsed)
        symbol = parsed['symbol']
        messageHash = 'myTrades'
        client.resolve(stored, messageHash)
        symbolSpecificMessageHash = 'myTrades:' + symbol
        client.resolve(stored, symbolSpecificMessageHash)

    def parse_ws_my_trade(self, trade, market=None):
        #
        # order and trade mixin(contract)
        #
        #   {
        #       accFillSz: '0.1',
        #       avgPx: '52.81',
        #       cTime: 1656511777208,
        #       clOrdId: '926043001195237376',
        #       execType: 'T',
        #       fillFee: '-0.0031686',
        #       fillFeeCcy: 'USDT',
        #       fillNotionalUsd: '5.281',
        #       fillPx: '52.81',
        #       fillSz: '0.1',
        #       fillTime: '1656511777266',
        #       force: 'normal',
        #       instId: 'LTCUSDT_UMCBL',
        #       lever: '1',
        #       notionalUsd: '5.281',
        #       ordId: '926043001132322816',
        #       ordType: 'market',
        #       orderFee: [Array],
        #       pnl: '0.004',
        #       posSide: 'long',
        #       px: '0',
        #       side: 'sell',
        #       status: 'full-fill',
        #       sz: '0.1',
        #       tdMode: 'cross',
        #       tgtCcy: 'USDT',
        #       tradeId: '926043001438552105',
        #       uTime: 1656511777266
        #   }
        #
        id = self.safe_string(trade, 'tradeId')
        orderId = self.safe_string(trade, 'ordId')
        marketId = self.safe_string(trade, 'instId')
        market = self.safe_market(marketId, market)
        timestamp = self.safe_integer(trade, 'fillTime')
        side = self.safe_string(trade, 'side')
        price = self.safe_string(trade, 'fillPx')
        amount = self.safe_string(trade, 'fillSz')
        type = self.safe_string(trade, 'ordType')
        cost = self.safe_string(trade, 'notional')
        feeCurrency = self.safe_string(trade, 'fillFeeCcy')
        feeAmount = Precise.string_abs(self.safe_string(trade, 'fillFee'))
        fee = {
            'code': self.safe_currency_code(feeCurrency),
            'cost': feeAmount,
        }
        return self.safe_trade({
            'info': trade,
            'id': id,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'type': type,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }, market)

    async def watch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict params: extra parameters specific to the bitget api endpoint
        :param str|None params['type']: spot or contract if not provided self.options['defaultType'] is used
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        type = None
        type, params = self.handle_market_type_and_params('watchOrders', None, params)
        sandboxMode = self.safe_value(self.options, 'sandboxMode', False)
        instType = 'spbl'
        if type == 'swap':
            instType = 'UMCBL'
            if sandboxMode:
                instType = 'S' + instType
        args = {
            'instType': instType,
            'channel': 'account',
            'instId': 'default',
        }
        messageHash = 'balance:' + instType.lower()
        return await self.watch_private(messageHash, messageHash, args, params)

    def handle_balance(self, client, message):
        # spot
        #
        #    {
        #        action: 'snapshot',
        #        arg: {instType: 'spbl', channel: 'account', instId: 'default'},
        #        data: [
        #          {coinId: '5', coinName: 'LTC', available: '0.1060938000000000'},
        #          {coinId: '2', coinName: 'USDT', available: '13.4498240000000000'}
        #        ]
        #    }
        #
        # swap
        #    {
        #      "action": "snapshot",
        #      "arg": {
        #        "instType": "umcbl",
        #        "channel": "account",
        #        "instId": "default"
        #      },
        #      "data": [
        #        {
        #          "marginCoin": "USDT",
        #          "locked": "0.00000000",
        #          "available": "3384.58046492",
        #          "maxOpenPosAvailable": "3384.58046492",
        #          "maxTransferOut": "3384.58046492",
        #          "equity": "3384.58046492",
        #          "usdtEquity": "3384.580464925690"
        #        }
        #      ]
        #    }
        #
        data = self.safe_value(message, 'data', [])
        for i in range(0, len(data)):
            rawBalance = data[i]
            currencyId = self.safe_string_2(rawBalance, 'coinName', 'marginCoin')
            code = self.safe_currency_code(currencyId)
            account = self.balance[code] if (code in self.balance) else self.account()
            account['free'] = self.safe_string(rawBalance, 'available')
            account['total'] = self.safe_string(rawBalance, 'equity')
            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']
        request = {
            'op': 'subscribe',
            'args': [args],
        }
        message = self.extend(request, params)
        return await self.watch(url, messageHash, message, messageHash)

    def authenticate(self, params={}):
        self.check_required_credentials()
        url = self.urls['api']['ws']
        client = self.client(url)
        messageHash = 'authenticated'
        future = self.safe_value(client.subscriptions, messageHash)
        if future 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 = {
                'op': operation,
                'args': [
                    {
                        'apiKey': self.apiKey,
                        'passphrase': self.password,
                        'timestamp': timestamp,
                        'sign': signature,
                    },
                ],
            }
            message = self.extend(request, params)
            future = self.watch(url, messageHash, message)
            client.subscriptions[messageHash] = future
        return future

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

    def handle_authenticate(self, client, message):
        #
        #  {event: 'login', code: 0}
        #
        messageHash = 'authenticated'
        client.resolve(message, messageHash)

    def handle_error_message(self, 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)
            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]
            return True

    def handle_message(self, client, message):
        #
        #   {
        #       action: 'snapshot',
        #       arg: {instType: 'sp', 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: 'spbl', 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 = {
            'ticker': self.handle_ticker,
            'trade': self.handle_trades,
            'orders': self.handle_order,
            'account': 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, message):
        client.lastPong = self.milliseconds()
        return message

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