Files
ccxt_with_mt5/ccxt/pro/bingx.py
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

1460 lines
60 KiB
Python

# -*- coding: utf-8 -*-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
import ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
from ccxt.base.types import Any, Balances, Int, Market, Order, OrderBook, Str, Ticker, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import BadRequest
from ccxt.base.errors import NotSupported
from ccxt.base.errors import NetworkError
class bingx(ccxt.async_support.bingx):
def describe(self) -> Any:
return self.deep_extend(super(bingx, self).describe(), {
'has': {
'ws': True,
'watchTrades': True,
'watchTradesForSymbols': False,
'watchOrderBook': True,
'watchOrderBookForSymbols': False, # no longer supported
'watchOHLCV': True,
'watchOHLCVForSymbols': False, # no longer supported
'watchOrders': True,
'watchMyTrades': True,
'watchTicker': True,
'watchTickers': False, # no longer supported
'watchBalance': True,
'unWatchOHLCV': True,
'unWatchOrderBook': True,
'unWatchTicker': True,
'unWatchTrades': True,
},
'urls': {
'api': {
'ws': {
'spot': 'wss://open-api-ws.bingx.com/market',
'linear': 'wss://open-api-swap.bingx.com/swap-market',
'inverse': 'wss://open-api-cswap-ws.bingx.com/market',
},
},
},
'options': {
'listenKeyRefreshRate': 3540000, # 1 hour(59 mins so we have 1 min to renew the token)
'ws': {
'gunzip': True,
},
'swap': {
'timeframes': {
'1m': '1m',
'3m': '3m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1h',
'2h': '2h',
'4h': '4h',
'6h': '6h',
'12h': '12h',
'1d': '1d',
'3d': '3d',
'1w': '1w',
'1M': '1M',
},
},
'spot': {
'timeframes': {
'1m': '1min',
'5m': '5min',
'15m': '15min',
'30m': '30min',
'1h': '60min',
'1d': '1day',
},
},
'watchBalance': {
'fetchBalanceSnapshot': True, # needed to be True to keep track of used and free balance
'awaitBalanceSnapshot': False, # whether to wait for the balance snapshot before providing updates
},
'watchOrderBook': {
'depth': 100, # 5, 10, 20, 50, 100
# 'interval': 500, # 100, 200, 500, 1000
},
'watchTrades': {
'ignoreDuplicates': True,
},
},
'streaming': {
'keepAlive': 1800000, # 30 minutes
},
})
async def un_watch(self, messageHash: str, subMessageHash: str, subscribeHash: str, dataType: str, topic: str, market: Market, methodName: str, params={}) -> Any:
marketType = None
subType = None
url = None
marketType, params = self.handle_market_type_and_params(methodName, market, params)
subType, params = self.handle_sub_type_and_params(methodName, market, params, 'linear')
if marketType == 'swap':
url = self.safe_string(self.urls['api']['ws'], subType)
else:
url = self.safe_string(self.urls['api']['ws'], marketType)
id = self.uuid()
request: dict = {
'id': id,
'dataType': dataType,
'reqType': 'unsub',
}
symbols = []
if market is not None:
symbols.append(market['symbol'])
subscription: dict = {
'unsubscribe': True,
'id': id,
'subMessageHashes': [subMessageHash],
'messageHashes': [messageHash],
'symbols': symbols,
'topic': topic,
}
symbolsAndTimeframes = self.safe_list(params, 'symbolsAndTimeframes')
if symbolsAndTimeframes is not None:
subscription['symbolsAndTimeframes'] = symbolsAndTimeframes
params = self.omit(params, 'symbolsAndTimeframes')
return await self.watch(url, messageHash, self.extend(request, params), subscribeHash, subscription)
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
https://bingx-api.github.io/docs/#/en-us/spot/socket/market.html#Subscribe%20to%2024-hour%20Price%20Change
https://bingx-api.github.io/docs/#/en-us/swapV2/socket/market.html#Subscribe%20to%2024-hour%20price%20changes
https://bingx-api.github.io/docs/#/en-us/cswap/socket/market.html#Subscribe%20to%2024-Hour%20Price%20Change
:param str symbol: unified symbol of the market to fetch the ticker for
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
market = self.market(symbol)
marketType = None
subType = None
url = None
marketType, params = self.handle_market_type_and_params('watchTicker', market, params)
subType, params = self.handle_sub_type_and_params('watchTicker', market, params, 'linear')
if marketType == 'swap':
url = self.safe_string(self.urls['api']['ws'], subType)
else:
url = self.safe_string(self.urls['api']['ws'], marketType)
dataType = market['id'] + '@ticker'
messageHash = self.get_message_hash('ticker', market['symbol'])
uuid = self.uuid()
request: dict = {
'id': uuid,
'dataType': dataType,
}
if marketType == 'swap':
request['reqType'] = 'sub'
subscription: dict = {
'unsubscribe': False,
'id': uuid,
}
return await self.watch(url, messageHash, self.extend(request, params), messageHash, subscription)
async def un_watch_ticker(self, symbol: str, params={}) -> Any:
"""
unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
https://bingx-api.github.io/docs/#/en-us/spot/socket/market.html#Subscribe%20to%2024-hour%20Price%20Change
https://bingx-api.github.io/docs/#/en-us/swapV2/socket/market.html#Subscribe%20to%2024-hour%20price%20changes
https://bingx-api.github.io/docs/#/en-us/cswap/socket/market.html#Subscribe%20to%2024-Hour%20Price%20Change
:param str symbol: unified symbol of the market to fetch the ticker for
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
market = self.market(symbol)
dataType = market['id'] + '@ticker'
subMessageHash = self.get_message_hash('ticker', market['symbol'])
messageHash = 'unsubscribe::' + subMessageHash
topic = 'ticker'
methodName = 'unWatchTicker'
return await self.un_watch(messageHash, subMessageHash, messageHash, dataType, topic, market, methodName, params)
def handle_ticker(self, client: Client, message):
#
# swap
#
# {
# "code": 0,
# "dataType": "BTC-USDT@ticker",
# "data": {
# "e": "24hTicker",
# "E": 1706498923556,
# "s": "BTC-USDT",
# "p": "346.4",
# "P": "0.82",
# "c": "42432.5",
# "L": "0.0529",
# "h": "42855.4",
# "l": "41578.3",
# "v": "64310.9754",
# "q": "2728360284.15",
# "o": "42086.1",
# "O": 1706498922655,
# "C": 1706498883023,
# "A": "42437.8",
# "a": "1.4160",
# "B": "42437.1",
# "b": "2.5747"
# }
# }
#
# spot
#
# {
# "code": 0,
# "timestamp": 1706506795473,
# "data": {
# "e": "24hTicker",
# "E": 1706506795472,
# "s": "BTC-USDT",
# "p": -372.12,
# "P": "-0.87%",
# "o": 42548.95,
# "h": 42696.1,
# "l": 41621.29,
# "c": 42176.83,
# "v": 4943.33,
# "q": 208842236.5,
# "O": 1706420395472,
# "C": 1706506795472,
# "A": 42177.23,
# "a": 5.14484,
# "B": 42176.38,
# "b": 5.36117
# }
# }
#
data = self.safe_value(message, 'data', {})
marketId = self.safe_string(data, 's')
# marketId = messageHash.split('@')[0]
isSwap = client.url.find('swap') >= 0
marketType = 'swap' if isSwap else 'spot'
market = self.safe_market(marketId, None, None, marketType)
symbol = market['symbol']
ticker = self.parse_ws_ticker(data, market)
self.tickers[symbol] = ticker
client.resolve(ticker, self.get_message_hash('ticker', symbol))
if self.safe_string(message, 'dataType') == 'all@ticker':
client.resolve(ticker, self.get_message_hash('ticker'))
def parse_ws_ticker(self, message, market=None):
#
# {
# "e": "24hTicker",
# "E": 1706498923556,
# "s": "BTC-USDT",
# "p": "346.4",
# "P": "0.82",
# "c": "42432.5",
# "L": "0.0529",
# "h": "42855.4",
# "l": "41578.3",
# "v": "64310.9754",
# "q": "2728360284.15",
# "o": "42086.1",
# "O": 1706498922655,
# "C": 1706498883023,
# "A": "42437.8",
# "a": "1.4160",
# "B": "42437.1",
# "b": "2.5747"
# }
#
timestamp = self.safe_integer(message, 'C')
marketId = self.safe_string(message, 's')
market = self.safe_market(marketId, market)
close = self.safe_string(message, 'c')
return self.safe_ticker({
'symbol': market['symbol'],
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'high': self.safe_string(message, 'h'),
'low': self.safe_string(message, 'l'),
'bid': self.safe_string(message, 'B'),
'bidVolume': self.safe_string(message, 'b'),
'ask': self.safe_string(message, 'A'),
'askVolume': self.safe_string(message, 'a'),
'vwap': None,
'open': self.safe_string(message, 'o'),
'close': close,
'last': close,
'previousClose': None,
'change': self.safe_string(message, 'p'),
'percentage': None,
'average': None,
'baseVolume': self.safe_string(message, 'v'),
'quoteVolume': self.safe_string(message, 'q'),
'info': message,
}, market)
def get_order_book_limit_by_market_type(self, marketType: str, limit: Int = None):
if limit is None:
limit = 100
else:
if marketType == 'swap' or marketType == 'future':
limit = self.find_nearest_ceiling([5, 10, 20, 50, 100], limit)
elif marketType == 'spot':
limit = self.find_nearest_ceiling([20, 100], limit)
return limit
def get_message_hash(self, unifiedChannel: str, symbol: Str = None, extra: Str = None):
hash = unifiedChannel
if symbol is not None:
hash += '::' + symbol
else:
hash += 's' # tickers, orderbooks, ohlcvs ...
if extra is not None:
hash += '::' + extra
return hash
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
watches information on multiple trades made in a market
https://bingx-api.github.io/docs/#/en-us/spot/socket/market.html#Subscription%20transaction%20by%20transaction
https://bingx-api.github.io/docs/#/en-us/swapV2/socket/market.html#Subscribe%20the%20Latest%20Trade%20Detail
https://bingx-api.github.io/docs/#/en-us/cswap/socket/market.html#Subscription%20transaction%20by%20transaction
: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
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
market = self.market(symbol)
symbol = market['symbol']
marketType = None
subType = None
url = None
marketType, params = self.handle_market_type_and_params('watchTrades', market, params)
subType, params = self.handle_sub_type_and_params('watchTrades', market, params, 'linear')
if marketType == 'swap':
url = self.safe_string(self.urls['api']['ws'], subType)
else:
url = self.safe_string(self.urls['api']['ws'], marketType)
rawHash = market['id'] + '@trade'
messageHash = 'trade::' + symbol
uuid = self.uuid()
request: dict = {
'id': uuid,
'dataType': rawHash,
}
if marketType == 'swap':
request['reqType'] = 'sub'
subscription: dict = {
'unsubscribe': False,
'id': uuid,
}
trades = await self.watch(url, messageHash, self.extend(request, params), messageHash, subscription)
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
result = self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
if self.handle_option('watchTrades', 'ignoreDuplicates', True):
filtered = self.remove_repeated_trades_from_array(result)
filtered = self.sort_by(filtered, 'timestamp')
return filtered
return result
async def un_watch_trades(self, symbol: str, params={}) -> Any:
"""
unsubscribes from the trades channel
https://bingx-api.github.io/docs/#/en-us/spot/socket/market.html#Subscription%20transaction%20by%20transaction
https://bingx-api.github.io/docs/#/en-us/swapV2/socket/market.html#Subscribe%20the%20Latest%20Trade%20Detail
https://bingx-api.github.io/docs/#/en-us/cswap/socket/market.html#Subscription%20transaction%20by%20transaction
:param str symbol: unified symbol of the market to fetch trades for
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.name]: the name of the method to call, 'trade' or 'aggTrade', default is 'trade'
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
"""
await self.load_markets()
market = self.market(symbol)
dataType = market['id'] + '@trade'
subMessageHash = self.get_message_hash('trade', market['symbol'])
messageHash = 'unsubscribe::' + subMessageHash
topic = 'trades'
methodName = 'unWatchTrades'
return await self.un_watch(messageHash, subMessageHash, messageHash, dataType, topic, market, methodName, params)
def handle_trades(self, client: Client, message):
#
# spot: first snapshot
#
# {
# "id": "d83b78ce-98be-4dc2-b847-12fe471b5bc5",
# "code": 0,
# "msg": "SUCCESS",
# "timestamp": 1690214699854
# }
#
# spot: subsequent updates
#
# {
# "code": 0,
# "data": {
# "E": 1690214529432,
# "T": 1690214529386,
# "e": "trade",
# "m": True,
# "p": "29110.19",
# "q": "0.1868",
# "s": "BTC-USDT",
# "t": "57903921"
# },
# "dataType": "BTC-USDT@trade",
# "success": True
# }
#
# linear swap: first snapshot
#
# {
# "id": "2aed93b1-6e1e-4038-aeba-f5eeaec2ca48",
# "code": 0,
# "msg": '',
# "dataType": '',
# "data": null
# }
#
# linear swap: subsequent updates
#
# {
# "code": 0,
# "dataType": "BTC-USDT@trade",
# "data": [
# {
# "q": "0.0421",
# "p": "29023.5",
# "T": 1690221401344,
# "m": False,
# "s": "BTC-USDT"
# },
# ...
# ]
# }
#
# inverse swap: first snapshot
#
# {
# "code": 0,
# "id": "a2e482ca-f71b-42f8-a83a-8ff85a713e64",
# "msg": "SUCCESS",
# "timestamp": 1722920589426
# }
#
# inverse swap: subsequent updates
#
# {
# "code": 0,
# "dataType": "BTC-USD@trade",
# "data": {
# "e": "trade",
# "E": 1722920589665,
# "s": "BTC-USD",
# "t": "39125001",
# "p": "55360.0",
# "q": "1",
# "T": 1722920589582,
# "m": False
# }
# }
#
data = self.safe_value(message, 'data', [])
rawHash = self.safe_string(message, 'dataType')
marketId = rawHash.split('@')[0]
isSwap = client.url.find('swap') >= 0
marketType = 'swap' if isSwap else 'spot'
market = self.safe_market(marketId, None, None, marketType)
symbol = market['symbol']
messageHash = 'trade::' + symbol
trades = None
if isinstance(data, list):
trades = self.parse_trades(data, market)
else:
trades = [self.parse_trade(data, market)]
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
for j in range(0, len(trades)):
stored.append(trades[j])
client.resolve(stored, messageHash)
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
https://bingx-api.github.io/docs/#/en-us/spot/socket/market.html#Subscribe%20Market%20Depth%20Data
https://bingx-api.github.io/docs/#/en-us/swapV2/socket/market.html#Subscribe%20Market%20Depth%20Data
https://bingx-api.github.io/docs/#/en-us/cswap/socket/market.html#Subscribe%20to%20Limited%20Depth
:param str symbol: unified symbol of the market to fetch the order book for
:param int [limit]: the maximum amount of order book entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
await self.load_markets()
market = self.market(symbol)
marketType = None
subType = None
url = None
marketType, params = self.handle_market_type_and_params('watchOrderBook', market, params)
subType, params = self.handle_sub_type_and_params('watchOrderBook', market, params, 'linear')
if marketType == 'swap':
url = self.safe_string(self.urls['api']['ws'], subType)
else:
url = self.safe_string(self.urls['api']['ws'], marketType)
options = self.safe_dict(self.options, 'watchOrderBook', {})
depth = self.safe_integer(options, 'depth', 100)
subscriptionHash = market['id'] + '@' + 'depth' + self.number_to_string(depth)
messageHash = self.get_message_hash('orderbook', market['symbol'])
uuid = self.uuid()
request: dict = {
'id': uuid,
'dataType': subscriptionHash,
}
if marketType == 'swap':
request['reqType'] = 'sub'
subscriptionArgs: dict = {}
if market['inverse']:
subscriptionArgs = {
'id': uuid,
'unsubscribe': False,
'count': limit,
'params': params,
}
else:
subscriptionArgs = {
'id': uuid,
'unsubscribe': False,
'level': limit,
'params': params,
}
orderbook = await self.watch(url, messageHash, self.deep_extend(request, params), subscriptionHash, subscriptionArgs)
return orderbook.limit()
async def un_watch_order_book(self, symbol: str, params={}) -> Any:
"""
unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://bingx-api.github.io/docs/#/en-us/spot/socket/market.html#Subscribe%20Market%20Depth%20Data
https://bingx-api.github.io/docs/#/en-us/swapV2/socket/market.html#Subscribe%20Market%20Depth%20Data
https://bingx-api.github.io/docs/#/en-us/cswap/socket/market.html#Subscribe%20to%20Limited%20Depth
:param str symbol: unified symbol of the market
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
await self.load_markets()
market = self.market(symbol)
options = self.safe_dict(self.options, 'watchOrderBook', {})
depth = self.safe_integer(options, 'depth', 100)
subMessageHash = market['id'] + '@' + 'depth' + self.number_to_string(depth)
messageHash = 'unsubscribe::' + subMessageHash
topic = 'orderbook'
methodName = 'unWatchOrderBook'
return await self.un_watch(messageHash, subMessageHash, messageHash, subMessageHash, topic, market, methodName, params)
def handle_delta(self, bookside, delta):
price = self.safe_float_2(delta, 0, 'p')
amount = self.safe_float_2(delta, 1, 'a')
bookside.store(price, amount)
def handle_order_book(self, client: Client, message):
#
# spot
#
# {
# "code":0,
# "data":
# {
# "asks":[
# ["84119.73","0.000011"],
# ["84116.52","0.000014"],
# ["84116.40","0.000039"]
# ],
# "bids":[
# ["83656.98","2.570805"],
# ["83655.51","0.000347"],
# ["83654.59","0.000082"]
# ],
# "lastUpdateId":13565694850
# },
# "dataType":"BTC-USDT@depth100",
# "success":true,
# "timestamp":1743241379958
# }
#
# linear swap
#
# {
# "code":0,
# "dataType":"BTC-USDT@depth100@500ms",
# "ts":1743241563651,
# "data":
# {
# "bids":[
# ["83363.2","0.1908"],
# ["83360.0","0.0003"],
# ["83356.5","0.0245"],
# ],
# "asks":[
# ["83495.0","0.0024"],
# ["83490.0","0.0001"],
# ["83488.0","0.0004"],
# ]
# }
# }
#
# inverse swap
#
# {
# "code":0,
# "dataType":"BTC-USD@depth100",
# "data":{
# "symbol":"BTC-USD",
# "bids":[
# {"p":"83411.2","a":"2.979216","v":"2485.0"},
# {"p":"83411.1","a":"1.592114","v":"1328.0"},
# {"p":"83410.8","a":"2.656730","v":"2216.0"},
# ],
# "asks":[
# {"p":"88200.0","a":"0.344671","v":"304.0"},
# {"p":"88023.8","a":"0.045442","v":"40.0"},
# {"p":"88001.0","a":"0.003409","v":"3.0"},
# ],
# "aggPrecision":"0.1",
# "timestamp":1743242290710
# }
# }
#
data = self.safe_dict(message, 'data', {})
dataType = self.safe_string(message, 'dataType')
parts = dataType.split('@')
firstPart = parts[0]
isAllEndpoint = (firstPart == 'all')
marketId = self.safe_string(data, 'symbol', firstPart)
isSwap = client.url.find('swap') >= 0
marketType = 'swap' if isSwap else 'spot'
market = self.safe_market(marketId, None, None, marketType)
symbol = market['symbol']
orderbook = self.safe_value(self.orderbooks, symbol)
if orderbook is None:
# limit = [5, 10, 20, 50, 100]
subscriptionHash = dataType
subscription = client.subscriptions[subscriptionHash]
limit = self.safe_integer(subscription, 'limit')
self.orderbooks[symbol] = self.order_book({}, limit)
orderbook = self.orderbooks[symbol]
snapshot = None
timestamp = self.safe_integer_2(message, 'timestamp', 'ts')
timestamp = self.safe_integer_2(data, 'timestamp', 'ts', timestamp)
if market['inverse']:
snapshot = self.parse_order_book(data, symbol, timestamp, 'bids', 'asks', 'p', 'a')
else:
snapshot = self.parse_order_book(data, symbol, timestamp, 'bids', 'asks', 0, 1)
nonce = self.safe_integer(data, 'lastUpdateId')
snapshot['nonce'] = nonce
orderbook.reset(snapshot)
messageHash = self.get_message_hash('orderbook', symbol)
client.resolve(orderbook, messageHash)
# resolve for "all"
if isAllEndpoint:
messageHashForAll = self.get_message_hash('orderbook')
client.resolve(orderbook, messageHashForAll)
def parse_ws_ohlcv(self, ohlcv, market=None) -> list:
#
# {
# "c": "28909.0",
# "o": "28915.4",
# "h": "28915.4",
# "l": "28896.1",
# "v": "27.6919",
# "T": 1696687499999,
# "t": 1696687440000
# }
#
# for spot, opening-time(t) is used instead of closing-time(T), to be compatible with fetchOHLCV
# for linear swap,(T) is the opening time
timestamp = 't' if (market['spot']) else 'T'
if market['swap']:
timestamp = 't' if (market['inverse']) else 'T'
return [
self.safe_integer(ohlcv, timestamp),
self.safe_number(ohlcv, 'o'),
self.safe_number(ohlcv, 'h'),
self.safe_number(ohlcv, 'l'),
self.safe_number(ohlcv, 'c'),
self.safe_number(ohlcv, 'v'),
]
def handle_ohlcv(self, client: Client, message):
#
# spot:
#
# {
# "code": 0,
# "data": {
# "E": 1696687498608,
# "K": {
# "T": 1696687499999,
# "c": "27917.829",
# "h": "27918.427",
# "i": "1min",
# "l": "27917.7",
# "n": 262,
# "o": "27917.91",
# "q": "25715.359197",
# "s": "BTC-USDT",
# "t": 1696687440000,
# "v": "0.921100"
# },
# "e": "kline",
# "s": "BTC-USDT"
# },
# "dataType": "BTC-USDT@kline_1min",
# "success": True
# }
#
# linear swap:
#
# {
# "code": 0,
# "dataType": "BTC-USDT@kline_1m",
# "s": "BTC-USDT",
# "data": [
# {
# "c": "28909.0",
# "o": "28915.4",
# "h": "28915.4",
# "l": "28896.1",
# "v": "27.6919",
# "T": 1690907580000
# }
# ]
# }
#
# inverse swap:
#
# {
# "code": 0,
# "timestamp": 1723769354547,
# "dataType": "BTC-USD@kline_1m",
# "data": {
# "t": 1723769340000,
# "o": 57485.1,
# "c": 57468,
# "l": 57464.9,
# "h": 57485.1,
# "a": 0.189663,
# "v": 109,
# "u": 92,
# "s": "BTC-USD"
# }
# }
#
isSwap = client.url.find('swap') >= 0
dataType = self.safe_string(message, 'dataType')
parts = dataType.split('@')
firstPart = parts[0]
isAllEndpoint = (firstPart == 'all')
marketId = self.safe_string(message, 's', firstPart)
marketType = 'swap' if isSwap else 'spot'
market = self.safe_market(marketId, None, None, marketType)
candles = None
if isSwap:
if market['inverse']:
candles = [self.safe_dict(message, 'data', {})]
else:
candles = self.safe_list(message, 'data', [])
else:
data = self.safe_dict(message, 'data', {})
candles = [self.safe_dict(data, 'K', {})]
symbol = market['symbol']
self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
rawTimeframe = dataType.split('_')[1]
marketOptions = self.safe_dict(self.options, marketType)
timeframes = self.safe_dict(marketOptions, 'timeframes', {})
unifiedTimeframe = self.find_timeframe(rawTimeframe, timeframes)
if self.safe_value(self.ohlcvs[symbol], rawTimeframe) is None:
subscriptionHash = dataType
subscription = client.subscriptions[subscriptionHash]
limit = self.safe_integer(subscription, 'limit')
self.ohlcvs[symbol][unifiedTimeframe] = ArrayCacheByTimestamp(limit)
stored = self.ohlcvs[symbol][unifiedTimeframe]
for i in range(0, len(candles)):
candle = candles[i]
parsed = self.parse_ws_ohlcv(candle, market)
stored.append(parsed)
resolveData = [symbol, unifiedTimeframe, stored]
messageHash = self.get_message_hash('ohlcv', symbol, unifiedTimeframe)
client.resolve(resolveData, messageHash)
# resolve for "all"
if isAllEndpoint:
messageHashForAll = self.get_message_hash('ohlcv', None, unifiedTimeframe)
client.resolve(resolveData, messageHashForAll)
async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
"""
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
https://bingx-api.github.io/docs/#/en-us/spot/socket/market.html#K-line%20Streams
https://bingx-api.github.io/docs/#/en-us/swapV2/socket/market.html#Subscribe%20K-Line%20Data
https://bingx-api.github.io/docs/#/en-us/cswap/socket/market.html#Subscribe%20to%20Latest%20Trading%20Pair%20K-Line
:param str symbol: unified symbol of the market to fetch OHLCV data for
:param str timeframe: the length of time each candle represents
:param int [since]: timestamp in ms of the earliest candle to fetch
:param int [limit]: the maximum amount of candles to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns int[][]: A list of candles ordered, open, high, low, close, volume
"""
await self.load_markets()
market = self.market(symbol)
marketType = None
subType = None
url = None
marketType, params = self.handle_market_type_and_params('watchOHLCV', market, params)
subType, params = self.handle_sub_type_and_params('watchOHLCV', market, params, 'linear')
if marketType == 'swap':
url = self.safe_string(self.urls['api']['ws'], subType)
else:
url = self.safe_string(self.urls['api']['ws'], marketType)
if url is None:
raise BadRequest(self.id + ' watchOHLCV is not supported for ' + marketType + ' markets.')
options = self.safe_value(self.options, marketType, {})
timeframes = self.safe_value(options, 'timeframes', {})
rawTimeframe = self.safe_string(timeframes, timeframe, timeframe)
messageHash = self.get_message_hash('ohlcv', market['symbol'], timeframe)
subscriptionHash = market['id'] + '@kline_' + rawTimeframe
uuid = self.uuid()
request: dict = {
'id': uuid,
'dataType': subscriptionHash,
}
if marketType == 'swap':
request['reqType'] = 'sub'
subscriptionArgs: dict = {
'id': uuid,
'unsubscribe': False,
'interval': rawTimeframe,
'params': params,
}
result = await self.watch(url, messageHash, self.extend(request, params), subscriptionHash, subscriptionArgs)
ohlcv = result[2]
if self.newUpdates:
limit = ohlcv.getLimit(symbol, limit)
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
async def un_watch_ohlcv(self, symbol: str, timeframe: str = '1m', params={}) -> Any:
"""
unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market
https://bingx-api.github.io/docs/#/en-us/spot/socket/market.html#K-line%20Streams
https://bingx-api.github.io/docs/#/en-us/swapV2/socket/market.html#Subscribe%20K-Line%20Data
https://bingx-api.github.io/docs/#/en-us/cswap/socket/market.html#Subscribe%20to%20Latest%20Trading%20Pair%20K-Line
:param str symbol: unified symbol of the market to fetch OHLCV data for
:param str timeframe: the length of time each candle represents
:param 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)
options = self.safe_value(self.options, market['type'], {})
timeframes = self.safe_value(options, 'timeframes', {})
rawTimeframe = self.safe_string(timeframes, timeframe, timeframe)
subMessageHash = market['id'] + '@kline_' + rawTimeframe
messageHash = 'unsubscribe::' + subMessageHash
topic = 'ohlcv'
methodName = 'unWatchOHLCV'
symbolsAndTimeframes = [[market['symbol'], timeframe]]
params['symbolsAndTimeframes'] = symbolsAndTimeframes
return await self.un_watch(messageHash, subMessageHash, messageHash, subMessageHash, topic, market, methodName, params)
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
watches information on multiple orders made by the user
https://bingx-api.github.io/docs/#/en-us/spot/socket/account.html#Subscription%20order%20update%20data
https://bingx-api.github.io/docs/#/en-us/swapV2/socket/account.html#Order%20update%20push
https://bingx-api.github.io/docs/#/en-us/cswap/socket/account.html#Order%20update%20push
:param str [symbol]: unified market symbol of the market orders are made in
:param int [since]: the earliest time in ms to watch orders for
:param int [limit]: the maximum number of order structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.authenticate()
type = None
subType = None
market = None
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
type, params = self.handle_market_type_and_params('watchOrders', market, params)
subType, params = self.handle_sub_type_and_params('watchOrders', market, params, 'linear')
isSpot = (type == 'spot')
spotHash = 'spot:private'
swapHash = 'swap:private'
subscriptionHash = spotHash if isSpot else swapHash
spotMessageHash = 'spot:order'
swapMessageHash = 'swap:order'
messageHash = spotMessageHash if isSpot else swapMessageHash
if market is not None:
messageHash += ':' + symbol
uuid = self.uuid()
baseUrl = None
request = None
if type == 'swap':
if subType == 'inverse':
raise NotSupported(self.id + ' watchOrders is not supported for inverse swap markets yet')
baseUrl = self.safe_string(self.urls['api']['ws'], subType)
else:
baseUrl = self.safe_string(self.urls['api']['ws'], type)
request = {
'id': uuid,
'reqType': 'sub',
'dataType': 'spot.executionReport',
}
url = baseUrl + '?listenKey=' + self.options['listenKey']
subscription: dict = {
'unsubscribe': False,
'id': uuid,
}
orders = await self.watch(url, messageHash, request, subscriptionHash, subscription)
if self.newUpdates:
limit = orders.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
watches information on multiple trades made by the user
https://bingx-api.github.io/docs/#/en-us/spot/socket/account.html#Subscription%20order%20update%20data
https://bingx-api.github.io/docs/#/en-us/swapV2/socket/account.html#Order%20update%20push
https://bingx-api.github.io/docs/#/en-us/cswap/socket/account.html#Order%20update%20push
:param str [symbol]: unified market symbol of the market the trades are made in
:param int [since]: the earliest time in ms to watch trades for
:param int [limit]: the maximum number of trade structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
"""
await self.load_markets()
await self.authenticate()
type = None
subType = None
market = None
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
type, params = self.handle_market_type_and_params('watchMyTrades', market, params)
subType, params = self.handle_sub_type_and_params('watchMyTrades', market, params, 'linear')
isSpot = (type == 'spot')
spotHash = 'spot:private'
swapHash = 'swap:private'
subscriptionHash = spotHash if isSpot else swapHash
spotMessageHash = 'spot:mytrades'
swapMessageHash = 'swap:mytrades'
messageHash = spotMessageHash if isSpot else swapMessageHash
if market is not None:
messageHash += ':' + symbol
uuid = self.uuid()
baseUrl = None
request = None
if type == 'swap':
if subType == 'inverse':
raise NotSupported(self.id + ' watchMyTrades is not supported for inverse swap markets yet')
baseUrl = self.safe_string(self.urls['api']['ws'], subType)
else:
baseUrl = self.safe_string(self.urls['api']['ws'], type)
request = {
'id': uuid,
'reqType': 'sub',
'dataType': 'spot.executionReport',
}
url = baseUrl + '?listenKey=' + self.options['listenKey']
subscription: dict = {
'unsubscribe': False,
'id': uuid,
}
trades = await self.watch(url, messageHash, request, subscriptionHash, subscription)
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
async def watch_balance(self, params={}) -> Balances:
"""
query for balance and get the amount of funds available for trading or funds locked in orders
https://bingx-api.github.io/docs/#/en-us/spot/socket/account.html#Subscription%20account%20balance%20push
https://bingx-api.github.io/docs/#/en-us/swapV2/socket/account.html#Account%20balance%20and%20position%20update%20push
https://bingx-api.github.io/docs/#/en-us/cswap/socket/account.html#Account%20balance%20and%20position%20update%20push
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
"""
await self.load_markets()
await self.authenticate()
type = None
subType = None
type, params = self.handle_market_type_and_params('watchBalance', None, params)
subType, params = self.handle_sub_type_and_params('watchBalance', None, params, 'linear')
isSpot = (type == 'spot')
spotSubHash = 'spot:balance'
swapSubHash = 'swap:private'
spotMessageHash = 'spot:balance'
swapMessageHash = 'swap:balance'
messageHash = spotMessageHash if isSpot else swapMessageHash
subscriptionHash = spotSubHash if isSpot else swapSubHash
request = None
baseUrl = None
uuid = self.uuid()
if type == 'swap':
if subType == 'inverse':
raise NotSupported(self.id + ' watchBalance is not supported for inverse swap markets yet')
baseUrl = self.safe_string(self.urls['api']['ws'], subType)
else:
baseUrl = self.safe_string(self.urls['api']['ws'], type)
request = {
'id': uuid,
'dataType': 'ACCOUNT_UPDATE',
}
url = baseUrl + '?listenKey=' + self.options['listenKey']
client = self.client(url)
self.set_balance_cache(client, type, subType, subscriptionHash, params)
fetchBalanceSnapshot = None
awaitBalanceSnapshot = None
fetchBalanceSnapshot, params = self.handle_option_and_params(params, 'watchBalance', 'fetchBalanceSnapshot', True)
awaitBalanceSnapshot, params = self.handle_option_and_params(params, 'watchBalance', 'awaitBalanceSnapshot', False)
if fetchBalanceSnapshot and awaitBalanceSnapshot:
await client.future(type + ':fetchBalanceSnapshot')
subscription: dict = {
'unsubscribe': False,
'id': uuid,
}
return await self.watch(url, messageHash, request, subscriptionHash, subscription)
def set_balance_cache(self, client: Client, type, subType, subscriptionHash, params):
if subscriptionHash in client.subscriptions:
return
fetchBalanceSnapshot = self.handle_option_and_params(params, 'watchBalance', 'fetchBalanceSnapshot', True)
if fetchBalanceSnapshot:
messageHash = type + ':fetchBalanceSnapshot'
if not (messageHash in client.futures):
client.future(messageHash)
self.spawn(self.load_balance_snapshot, client, messageHash, type, subType)
else:
self.balance[type] = {}
async def load_balance_snapshot(self, client, messageHash, type, subType):
response = await self.fetch_balance({'type': type, 'subType': subType})
self.balance[type] = self.extend(response, self.safe_value(self.balance, type, {}))
# don't remove the future from the .futures cache
future = client.futures[messageHash]
future.resolve()
client.resolve(self.balance[type], type + ':balance')
def handle_error_message(self, client, message):
#
# {code: 100400, msg: '', timestamp: 1696245808833}
#
# {
# "code": 100500,
# "id": "9cd37d32-da98-440b-bd04-37e7dbcf51ad",
# "msg": '',
# "timestamp": 1696245842307
# }
code = self.safe_string(message, 'code')
try:
if code is not None:
feedback = self.id + ' ' + self.json(message)
self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
except Exception as e:
client.reject(e)
return True
async def keep_alive_listen_key(self, params={}):
listenKey = self.safe_string(self.options, 'listenKey')
if listenKey is None:
# A network error happened: we can't renew a listen key that does not exist.
return
try:
await self.userAuthPrivatePutUserDataStream({'listenKey': listenKey}) # self.extend the expiry
except Exception as error:
types = ['spot', 'linear', 'inverse']
for i in range(0, len(types)):
type = types[i]
url = self.urls['api']['ws'][type] + '?listenKey=' + listenKey
client = self.client(url)
messageHashes = list(client.futures.keys())
for j in range(0, len(messageHashes)):
messageHash = messageHashes[j]
client.reject(error, messageHash)
self.options['listenKey'] = None
self.options['lastAuthenticatedTime'] = 0
return
# whether or not to schedule another listenKey keepAlive request
listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 3600000)
self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params)
async def authenticate(self, params={}):
time = self.milliseconds()
lastAuthenticatedTime = self.safe_integer(self.options, 'lastAuthenticatedTime', 0)
listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 3600000) # 1 hour
if time - lastAuthenticatedTime > listenKeyRefreshRate:
response = await self.userAuthPrivatePostUserDataStream()
self.options['listenKey'] = self.safe_string(response, 'listenKey')
self.options['lastAuthenticatedTime'] = time
self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params)
async def pong(self, client, message):
#
# spot
# {
# "ping": "5963ba3db76049b2870f9a686b2ebaac",
# "time": "2023-10-02T18:51:55.089+0800"
# }
# swap
# Ping
#
try:
if message == 'Ping':
await client.send('Pong')
else:
ping = self.safe_string(message, 'ping')
time = self.safe_string(message, 'time')
await client.send({
'pong': ping,
'time': time,
})
except Exception as e:
error = NetworkError(self.id + ' pong failed with error ' + self.json(e))
client.reset(error)
def handle_order(self, client, message):
#
# {
# "code": 0,
# "dataType": "spot.executionReport",
# "data": {
# "e": "executionReport",
# "E": 1694680212947,
# "s": "LTC-USDT",
# "S": "BUY",
# "o": "LIMIT",
# "q": 0.1,
# "p": 50,
# "x": "NEW",
# "X": "PENDING",
# "i": 1702238305204043800,
# "l": 0,
# "z": 0,
# "L": 0,
# "n": 0,
# "N": "",
# "T": 0,
# "t": 0,
# "O": 1694680212676,
# "Z": 0,
# "Y": 0,
# "Q": 0,
# "m": False
# }
# }
#
# {
# "code": 0,
# "dataType": "spot.executionReport",
# "data": {
# "e": "executionReport",
# "E": 1694681809302,
# "s": "LTC-USDT",
# "S": "BUY",
# "o": "MARKET",
# "q": 0,
# "p": 62.29,
# "x": "TRADE",
# "X": "FILLED",
# "i": "1702245001712369664",
# "l": 0.0802,
# "z": 0.0802,
# "L": 62.308,
# "n": -0.0000802,
# "N": "LTC",
# "T": 1694681809256,
# "t": 38259147,
# "O": 1694681809248,
# "Z": 4.9971016,
# "Y": 4.9971016,
# "Q": 5,
# "m": False
# }
# }
# swap
# {
# "e": "ORDER_TRADE_UPDATE",
# "E": 1696843635475,
# "o": {
# "s": "LTC-USDT",
# "c": "",
# "i": "1711312357852147712",
# "S": "BUY",
# "o": "MARKET",
# "q": "0.10000000",
# "p": "64.35010000",
# "ap": "64.36000000",
# "x": "TRADE",
# "X": "FILLED",
# "N": "USDT",
# "n": "-0.00321800",
# "T": 0,
# "wt": "MARK_PRICE",
# "ps": "LONG",
# "rp": "0.00000000",
# "z": "0.10000000"
# }
# }
#
isSpot = ('dataType' in message)
data = self.safe_value_2(message, 'data', 'o', {})
if self.orders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
stored = self.orders
parsedOrder = self.parse_order(data)
stored.append(parsedOrder)
symbol = parsedOrder['symbol']
spotHash = 'spot:order'
swapHash = 'swap:order'
messageHash = spotHash if (isSpot) else swapHash
client.resolve(stored, messageHash)
client.resolve(stored, messageHash + ':' + symbol)
def handle_my_trades(self, client: Client, message):
#
#
# {
# "code": 0,
# "dataType": "spot.executionReport",
# "data": {
# "e": "executionReport",
# "E": 1694681809302,
# "s": "LTC-USDT",
# "S": "BUY",
# "o": "MARKET",
# "q": 0,
# "p": 62.29,
# "x": "TRADE",
# "X": "FILLED",
# "i": "1702245001712369664",
# "l": 0.0802,
# "z": 0.0802,
# "L": 62.308,
# "n": -0.0000802,
# "N": "LTC",
# "T": 1694681809256,
# "t": 38259147,
# "O": 1694681809248,
# "Z": 4.9971016,
# "Y": 4.9971016,
# "Q": 5,
# "m": False
# }
# }
#
# swap
# {
# "e": "ORDER_TRADE_UPDATE",
# "E": 1696843635475,
# "o": {
# "s": "LTC-USDT",
# "c": "",
# "i": "1711312357852147712",
# "S": "BUY",
# "o": "MARKET",
# "q": "0.10000000",
# "p": "64.35010000",
# "ap": "64.36000000",
# "x": "TRADE",
# "X": "FILLED",
# "N": "USDT",
# "n": "-0.00321800",
# "T": 0,
# "wt": "MARK_PRICE",
# "ps": "LONG",
# "rp": "0.00000000",
# "z": "0.10000000"
# }
# }
#
isSpot = ('dataType' in message)
result = self.safe_dict_2(message, 'data', 'o', {})
cachedTrades = self.myTrades
if cachedTrades is None:
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
cachedTrades = ArrayCacheBySymbolById(limit)
self.myTrades = cachedTrades
type = 'spot' if isSpot else 'swap'
marketId = self.safe_string(result, 's')
market = self.safe_market(marketId, None, '-', type)
parsed = self.parse_trade(result, market)
symbol = parsed['symbol']
spotHash = 'spot:mytrades'
swapHash = 'swap:mytrades'
messageHash = spotHash if isSpot else swapHash
cachedTrades.append(parsed)
client.resolve(cachedTrades, messageHash)
client.resolve(cachedTrades, messageHash + ':' + symbol)
def handle_balance(self, client: Client, message):
# spot
# {
# "e":"ACCOUNT_UPDATE",
# "E":1696242817000,
# "T":1696242817142,
# "a":{
# "B":[
# {
# "a":"USDT",
# "bc":"-1.00000000000000000000",
# "cw":"86.59497382000000050000",
# "wb":"86.59497382000000050000"
# }
# ],
# "m":"ASSET_TRANSFER"
# }
# }
# swap
# {
# "e":"ACCOUNT_UPDATE",
# "E":1696244249320,
# "a":{
# "m":"WITHDRAW",
# "B":[
# {
# "a":"USDT",
# "wb":"49.81083984",
# "cw":"49.81083984",
# "bc":"-1.00000000"
# }
# ],
# "P":[
# ]
# }
# }
#
a = self.safe_dict(message, 'a', {})
data = self.safe_list(a, 'B', [])
timestamp = self.safe_integer_2(message, 'T', 'E')
type = 'swap' if ('P' in a) else 'spot'
if not (type in self.balance):
self.balance[type] = {}
self.balance[type]['info'] = data
self.balance[type]['timestamp'] = timestamp
self.balance[type]['datetime'] = self.iso8601(timestamp)
for i in range(0, len(data)):
balance = data[i]
currencyId = self.safe_string(balance, 'a')
code = self.safe_currency_code(currencyId)
account = self.account()
account['info'] = balance
account['used'] = self.safe_string(balance, 'lk')
account['free'] = self.safe_string(balance, 'wb')
self.balance[type][code] = account
self.balance[type] = self.safe_balance(self.balance[type])
client.resolve(self.balance[type], type + ':balance')
def handle_message(self, client: Client, message):
if not self.handle_error_message(client, message):
return
# public subscriptions
if (message == 'Ping') or ('ping' in message):
self.spawn(self.pong, client, message)
return
dataType = self.safe_string(message, 'dataType', '')
if dataType.find('@depth') >= 0:
self.handle_order_book(client, message)
return
if dataType.find('@ticker') >= 0:
self.handle_ticker(client, message)
return
if dataType.find('@trade') >= 0:
self.handle_trades(client, message)
return
if dataType.find('@kline') >= 0:
self.handle_ohlcv(client, message)
return
if dataType.find('executionReport') >= 0:
data = self.safe_value(message, 'data', {})
type = self.safe_string(data, 'x')
if type == 'TRADE':
self.handle_my_trades(client, message)
self.handle_order(client, message)
return
e = self.safe_string(message, 'e')
if e == 'ACCOUNT_UPDATE':
self.handle_balance(client, message)
if e == 'ORDER_TRADE_UPDATE':
self.handle_order(client, message)
data = self.safe_value(message, 'o', {})
type = self.safe_string(data, 'x')
status = self.safe_string(data, 'X')
if (type == 'TRADE') and (status == 'FILLED'):
self.handle_my_trades(client, message)
msgData = self.safe_value(message, 'data')
msgEvent = self.safe_string(msgData, 'e')
if msgEvent == '24hTicker':
self.handle_ticker(client, message)
if dataType == '' and msgEvent is None and e is None:
self.handle_subscription_status(client, message)
def handle_subscription_status(self, client: Client, message):
#
# {
# "code": 0,
# "id": "b6ed9cb4-f3d0-4641-ac3f-f59eb47a3abd",
# "msg": "SUCCESS",
# "timestamp": 1759225965363
# }
#
id = self.safe_string(message, 'id')
subscriptionsById = self.index_by(client.subscriptions, 'id')
subscription = self.safe_dict(subscriptionsById, id, {})
isUnSubMessage = self.safe_bool(subscription, 'unsubscribe', False)
if isUnSubMessage:
self.handle_un_subscription(client, subscription)
return message
def handle_un_subscription(self, client: Client, subscription: dict):
messageHashes = self.safe_list(subscription, 'messageHashes', [])
subMessageHashes = self.safe_list(subscription, 'subMessageHashes', [])
for i in range(0, len(messageHashes)):
unsubHash = messageHashes[i]
subHash = subMessageHashes[i]
self.clean_unsubscription(client, subHash, unsubHash)
self.clean_cache(subscription)