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

1579 lines
66 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, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp
from ccxt.async_support.base.ws.order_book_side import Asks, Bids
import hashlib
from ccxt.base.types import Any, Balances, Bool, Int, Market, Order, OrderBook, Position, Str, Strings, Ticker, Tickers, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import NotSupported
class bitmart(ccxt.async_support.bitmart):
def describe(self) -> Any:
return self.deep_extend(super(bitmart, self).describe(), {
'has': {
'createOrderWs': False,
'editOrderWs': False,
'fetchOpenOrdersWs': False,
'fetchOrderWs': False,
'cancelOrderWs': False,
'cancelOrdersWs': False,
'cancelAllOrdersWs': False,
'ws': True,
'watchBalance': True,
'watchTicker': True,
'watchTickers': True,
'watchBidsAsks': True,
'watchOrderBook': True,
'watchOrderBookForSymbols': True,
'watchOrders': True,
'watchTrades': True,
'watchTradesForSymbols': True,
'watchOHLCV': True,
'watchPosition': 'emulated',
'watchPositions': True,
},
'urls': {
'api': {
'ws': {
'spot': {
'public': 'wss://ws-manager-compress.{hostname}/api?protocol=1.1',
'private': 'wss://ws-manager-compress.{hostname}/user?protocol=1.1',
},
'swap': {
'public': 'wss://openapi-ws-v2.{hostname}/api?protocol=1.1',
'private': 'wss://openapi-ws-v2.{hostname}/user?protocol=1.1',
},
},
},
},
'options': {
'defaultType': 'spot',
'watchBalance': {
'fetchBalanceSnapshot': True, # or False
'awaitBalanceSnapshot': False, # whether to wait for the balance snapshot before providing updates
},
#
# orderbook channels can have:
# - 'depth5', 'depth20', 'depth50' # these endpoints emit full Orderbooks once in every 500ms
# - 'depth/increase100' # self endpoint is preferred, because it emits once in 100ms. however, when self value is chosen, it only affects spot-market, but contracts markets automatically `depth50` will be being used
'watchOrderBook': {
'depth': 'depth/increase100',
},
'watchOrderBookForSymbols': {
'depth': 'depth/increase100',
},
'watchTrades': {
'ignoreDuplicates': True,
},
'ws': {
'inflate': True,
},
'timeframes': {
'1m': '1m',
'3m': '3m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'45m': '45m',
'1h': '1H',
'2h': '2H',
'3h': '3H',
'4h': '4H',
'1d': '1D',
'1w': '1W',
'1M': '1M',
},
},
'streaming': {
'keepAlive': 15000,
},
})
async def subscribe(self, channel, symbol, type, params={}):
market = self.market(symbol)
url = self.implode_hostname(self.urls['api']['ws'][type]['public'])
request = {}
messageHash = None
if type == 'spot':
messageHash = 'spot/' + channel + ':' + market['id']
request = {
'op': 'subscribe',
'args': [messageHash],
}
else:
messageHash = 'futures/' + channel + ':' + market['id']
speed = self.safe_string(params, 'speed')
if speed is not None:
params = self.omit(params, 'speed')
messageHash += ':' + speed
request = {
'action': 'subscribe',
'args': [messageHash],
}
return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
async def subscribe_multiple(self, channel: str, type: str, symbols: Strings = None, params={}):
symbols = self.market_symbols(symbols, type, False, True)
url = self.implode_hostname(self.urls['api']['ws'][type]['public'])
channelType = 'spot' if (type == 'spot') else 'futures'
actionType = 'op' if (type == 'spot') else 'action'
rawSubscriptions = []
messageHashes = []
for i in range(0, len(symbols)):
market = self.market(symbols[i])
message = channelType + '/' + channel + ':' + market['id']
rawSubscriptions.append(message)
messageHashes.append(channel + ':' + market['symbol'])
# exclusion, futures "tickers" need one generic request for all symbols
# if (type != 'spot') and (channel == 'ticker'):
# rawSubscriptions = [channelType + '/' + channel]
# }
# Exchange update from 2025-02-11 supports subscription by trading pair for swap
request: dict = {
'args': rawSubscriptions,
}
request[actionType] = 'subscribe'
return await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), rawSubscriptions)
async def watch_balance(self, params={}) -> Balances:
"""
https://developer-pro.bitmart.com/en/spot/#private-balance-change
https://developer-pro.bitmart.com/en/futuresv2/#private-assets-channel
watch balance and get the amount of funds available for trading or funds locked in orders
: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()
type = 'spot'
type, params = self.handle_market_type_and_params('watchBalance', None, params)
await self.authenticate(type, params)
request = {}
if type == 'spot':
request = {
'op': 'subscribe',
'args': ['spot/user/balance:BALANCE_UPDATE'],
}
else:
request = {
'action': 'subscribe',
'args': ['futures/asset:USDT', 'futures/asset:BTC', 'futures/asset:ETH'],
}
messageHash = 'balance:' + type
url = self.implode_hostname(self.urls['api']['ws'][type]['private'])
client = self.client(url)
self.set_balance_cache(client, type, messageHash)
fetchBalanceSnapshot = None
awaitBalanceSnapshot = None
fetchBalanceSnapshot, params = self.handle_option_and_params(self.options, 'watchBalance', 'fetchBalanceSnapshot', True)
awaitBalanceSnapshot, params = self.handle_option_and_params(self.options, 'watchBalance', 'awaitBalanceSnapshot', False)
if fetchBalanceSnapshot and awaitBalanceSnapshot:
await client.future(type + ':fetchBalanceSnapshot')
return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
def set_balance_cache(self, client: Client, type, subscribeHash):
if subscribeHash in client.subscriptions:
return
options = self.safe_value(self.options, 'watchBalance')
snapshot = self.safe_bool(options, 'fetchBalanceSnapshot', True)
if snapshot:
messageHash = type + ':' + 'fetchBalanceSnapshot'
if not (messageHash in client.futures):
client.future(messageHash)
self.spawn(self.load_balance_snapshot, client, messageHash, type)
self.balance[type] = {}
# without self comment, transpilation breaks for some reason...
async def load_balance_snapshot(self, client, messageHash, type):
response = await self.fetch_balance({'type': type})
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], 'balance:' + type)
def handle_balance(self, client: Client, message):
#
# spot
# {
# "data":[
# {
# "balance_details":[
# {
# "av_bal":"0.206000000000000000000000000000",
# "ccy":"LTC",
# "fz_bal":"0.100000000000000000000000000000"
# }
# ],
# "event_time":"1701632345416",
# "event_type":"TRANSACTION_COMPLETED"
# }
# ],
# "table":"spot/user/balance"
# }
# swap
# {
# group: 'futures/asset:USDT',
# data: {
# currency: 'USDT',
# available_balance: '37.19688649135',
# position_deposit: '0.788687546',
# frozen_balance: '0'
# }
# }
#
channel = self.safe_string_2(message, 'table', 'group')
data = self.safe_value(message, 'data')
if data is None:
return
isSpot = (channel.find('spot') >= 0)
type = 'spot' if isSpot else 'swap'
self.balance[type]['info'] = message
if isSpot:
if not isinstance(data, list):
return
for i in range(0, len(data)):
timestamp = self.safe_integer(message, 'event_time')
self.balance[type]['timestamp'] = timestamp
self.balance[type]['datetime'] = self.iso8601(timestamp)
balanceDetails = self.safe_value(data[i], 'balance_details', [])
for ii in range(0, len(balanceDetails)):
rawBalance = balanceDetails[i]
account = self.account()
currencyId = self.safe_string(rawBalance, 'ccy')
code = self.safe_currency_code(currencyId)
account['free'] = self.safe_string(rawBalance, 'av_bal')
account['used'] = self.safe_string(rawBalance, 'fz_bal')
self.balance[type][code] = account
else:
currencyId = self.safe_string(data, 'currency')
code = self.safe_currency_code(currencyId)
account = self.account()
account['free'] = self.safe_string(data, 'available_balance')
account['used'] = self.safe_string(data, 'frozen_balance')
self.balance[type][code] = account
self.balance[type] = self.safe_balance(self.balance[type])
messageHash = 'balance:' + type
client.resolve(self.balance[type], messageHash)
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
https://developer-pro.bitmart.com/en/spot/#public-trade-channel
https://developer-pro.bitmart.com/en/futuresv2/#public-trade-channel
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 [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.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]:
"""
https://developer-pro.bitmart.com/en/spot/#public-trade-channel
get the list of most recent trades for a list of symbols
:param str[] symbols: 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.com/#/?id=public-trades>`
"""
await self.load_markets()
marketType = None
symbols, marketType, params = self.get_params_for_multiple_sub('watchTradesForSymbols', symbols, limit, params)
channelName = 'trade'
trades = await self.subscribe_multiple(channelName, marketType, symbols, params)
if self.newUpdates:
first = self.safe_dict(trades, 0)
tradeSymbol = self.safe_string(first, 'symbol')
limit = trades.getLimit(tradeSymbol, 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
def get_params_for_multiple_sub(self, methodName: str, symbols: List[str], limit: Int = None, params={}):
symbols = self.market_symbols(symbols, None, False, True)
length = len(symbols)
if length > 20:
raise NotSupported(self.id + ' ' + methodName + '() accepts a maximum of 20 symbols in one request')
market = self.market(symbols[0])
marketType = None
marketType, params = self.handle_market_type_and_params(methodName, market, params)
return [symbols, marketType, params]
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
"""
https://developer-pro.bitmart.com/en/spot/#public-ticker-channel
https://developer-pro.bitmart.com/en/futuresv2/#public-ticker-channel
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
:param str symbol: unified symbol of the market to fetch the ticker for
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
symbol = self.symbol(symbol)
tickers = await self.watch_tickers([symbol], params)
return tickers[symbol]
async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
"""
https://developer-pro.bitmart.com/en/spot/#public-ticker-channel
https://developer-pro.bitmart.com/en/futuresv2/#public-ticker-channel
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
:param str[] symbols: unified symbol of the market to fetch the ticker for
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
market = self.get_market_from_symbols(symbols)
marketType = None
marketType, params = self.handle_market_type_and_params('watchTickers', market, params)
ticker = await self.subscribe_multiple('ticker', marketType, symbols, params)
if self.newUpdates:
tickers: dict = {}
tickers[ticker['symbol']] = ticker
return tickers
return self.filter_by_array(self.tickers, 'symbol', symbols)
async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
"""
https://developer-pro.bitmart.com/en/spot/#public-ticker-channel
https://developer-pro.bitmart.com/en/futuresv2/#public-ticker-channel
watches best bid & ask for symbols
:param str[] symbols: unified symbol of the market to fetch the ticker for
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
symbols = self.market_symbols(symbols, None, False)
firstMarket = self.get_market_from_symbols(symbols)
marketType = None
marketType, params = self.handle_market_type_and_params('watchBidsAsks', firstMarket, params)
url = self.implode_hostname(self.urls['api']['ws'][marketType]['public'])
channelType = 'spot' if (marketType == 'spot') else 'futures'
actionType = 'op' if (marketType == 'spot') else 'action'
rawSubscriptions = []
messageHashes = []
for i in range(0, len(symbols)):
market = self.market(symbols[i])
rawSubscriptions.append(channelType + '/ticker:' + market['id'])
messageHashes.append('bidask:' + symbols[i])
if marketType != 'spot':
rawSubscriptions = [channelType + '/ticker']
request: dict = {
'args': rawSubscriptions,
}
request[actionType] = 'subscribe'
newTickers = await self.watch_multiple(url, messageHashes, request, rawSubscriptions)
if self.newUpdates:
tickers: dict = {}
tickers[newTickers['symbol']] = newTickers
return tickers
return self.filter_by_array(self.bidsasks, 'symbol', symbols)
def handle_bid_ask(self, client: Client, message):
table = self.safe_string(message, 'table')
isSpot = (table is not None)
rawTickers = []
if isSpot:
rawTickers = self.safe_list(message, 'data', [])
else:
rawTickers = [self.safe_value(message, 'data', {})]
if not len(rawTickers):
return
for i in range(0, len(rawTickers)):
ticker = self.parse_ws_bid_ask(rawTickers[i])
symbol = ticker['symbol']
self.bidsasks[symbol] = ticker
messageHash = 'bidask:' + symbol
client.resolve(ticker, messageHash)
def parse_ws_bid_ask(self, ticker, market=None):
marketId = self.safe_string(ticker, 'symbol')
market = self.safe_market(marketId, market)
symbol = self.safe_string(market, 'symbol')
timestamp = self.safe_integer(ticker, 'ms_t')
return self.safe_ticker({
'symbol': symbol,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'ask': self.safe_string_2(ticker, 'ask_px', 'ask_price'),
'askVolume': self.safe_string_2(ticker, 'ask_sz', 'ask_vol'),
'bid': self.safe_string_2(ticker, 'bid_px', 'bid_price'),
'bidVolume': self.safe_string_2(ticker, 'bid_sz', 'bid_vol'),
'info': ticker,
}, market)
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://developer-pro.bitmart.com/en/spot/#private-order-progress
https://developer-pro.bitmart.com/en/futuresv2/#private-order-channel
: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 = None
messageHash = 'orders'
if symbol is not None:
symbol = self.symbol(symbol)
market = self.market(symbol)
messageHash = 'orders::' + symbol
type = 'spot'
type, params = self.handle_market_type_and_params('watchOrders', market, params)
await self.authenticate(type, params)
request = None
if type == 'spot':
argsRequest = 'spot/user/order:'
if symbol is not None:
argsRequest += market['id']
else:
argsRequest = 'spot/user/orders:ALL_SYMBOLS'
request = {
'op': 'subscribe',
'args': [argsRequest],
}
else:
request = {
'action': 'subscribe',
'args': ['futures/order'],
}
url = self.implode_hostname(self.urls['api']['ws'][type]['private'])
newOrders = await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
if self.newUpdates:
return newOrders
return self.filter_by_symbol_since_limit(self.orders, symbol, since, limit, True)
def handle_orders(self, client: Client, message):
#
# spot
# {
# "data":[
# {
# "symbol": "LTC_USDT",
# "notional": '',
# "side": "buy",
# "last_fill_time": "0",
# "ms_t": "1646216634000",
# "type": "limit",
# "filled_notional": "0.000000000000000000000000000000",
# "last_fill_price": "0",
# "size": "0.500000000000000000000000000000",
# "price": "50.000000000000000000000000000000",
# "last_fill_count": "0",
# "filled_size": "0.000000000000000000000000000000",
# "margin_trading": "0",
# "state": "8",
# "order_id": "24807076628",
# "order_type": "0"
# }
# ],
# "table":"spot/user/order"
# }
# swap
# {
# "group":"futures/order",
# "data":[
# {
# "action":2,
# "order":{
# "order_id":"2312045036986775",
# "client_order_id":"",
# "price":"71.61707928",
# "size":"1",
# "symbol":"LTCUSDT",
# "state":1,
# "side":4,
# "type":"market",
# "leverage":"1",
# "open_type":"cross",
# "deal_avg_price":"0",
# "deal_size":"0",
# "create_time":1701625324646,
# "update_time":1701625324640,
# "plan_order_id":"",
# "last_trade":null
# }
# }
# ]
# }
#
orders = self.safe_value(message, 'data')
if orders is None:
return
ordersLength = len(orders)
newOrders = []
symbols: dict = {}
if ordersLength > 0:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
if self.orders is None:
self.orders = ArrayCacheBySymbolById(limit)
stored = self.orders
for i in range(0, len(orders)):
order = self.parse_ws_order(orders[i])
stored.append(order)
newOrders.append(order)
symbol = order['symbol']
symbols[symbol] = True
messageHash = 'orders'
symbolKeys = list(symbols.keys())
for i in range(0, len(symbolKeys)):
symbol = symbolKeys[i]
symbolSpecificMessageHash = messageHash + '::' + symbol
client.resolve(newOrders, symbolSpecificMessageHash)
client.resolve(newOrders, messageHash)
def parse_ws_order(self, order: dict, market: Market = None):
#
# spot
# {
# "symbol": "LTC_USDT",
# "notional": '',
# "side": "buy",
# "last_fill_time": "0",
# "ms_t": "1646216634000",
# "type": "limit",
# "filled_notional": "0.000000000000000000000000000000",
# "last_fill_price": "0",
# "size": "0.500000000000000000000000000000",
# "price": "50.000000000000000000000000000000",
# "last_fill_count": "0",
# "filled_size": "0.000000000000000000000000000000",
# "margin_trading": "0",
# "state": "8",
# "order_id": "24807076628",
# "order_type": "0"
# }
# swap
# {
# "action":2,
# "order":{
# "order_id":"2312045036986775",
# "client_order_id":"",
# "price":"71.61707928",
# "size":"1",
# "symbol":"LTCUSDT",
# "state":1,
# "side":4,
# "type":"market",
# "leverage":"1",
# "open_type":"cross",
# "deal_avg_price":"0",
# "deal_size":"0",
# "create_time":1701625324646,
# "update_time":1701625324640,
# "plan_order_id":"",
# "last_trade":null
# }
# }
#
action = self.safe_number(order, 'action')
isSpot = (action is None)
if isSpot:
marketId = self.safe_string(order, 'symbol')
market = self.safe_market(marketId, market, '_', 'spot')
id = self.safe_string(order, 'order_id')
clientOrderId = self.safe_string(order, 'clientOid')
price = self.safe_string(order, 'price')
filled = self.safe_string(order, 'filled_size')
amount = self.safe_string(order, 'size')
type = self.safe_string(order, 'type')
rawState = self.safe_string(order, 'state')
status = self.parse_order_status_by_type(market['type'], rawState)
timestamp = self.safe_integer(order, 'ms_t')
symbol = market['symbol']
side = self.safe_string_lower(order, 'side')
return self.safe_order({
'info': order,
'symbol': symbol,
'id': id,
'clientOrderId': clientOrderId,
'timestamp': None,
'datetime': None,
'lastTradeTimestamp': timestamp,
'type': type,
'timeInForce': None,
'postOnly': None,
'side': side,
'price': price,
'stopPrice': None,
'triggerPrice': None,
'amount': amount,
'cost': None,
'average': None,
'filled': filled,
'remaining': None,
'status': status,
'fee': None,
'trades': None,
}, market)
else:
orderInfo = self.safe_value(order, 'order')
marketId = self.safe_string(orderInfo, 'symbol')
symbol = self.safe_symbol(marketId, market, '', 'swap')
orderId = self.safe_string(orderInfo, 'order_id')
timestamp = self.safe_integer(orderInfo, 'create_time')
updatedTimestamp = self.safe_integer(orderInfo, 'update_time')
lastTrade = self.safe_value(orderInfo, 'last_trade')
cachedOrders = self.orders
orders = self.safe_value(cachedOrders.hashmap, symbol, {})
cachedOrder = self.safe_value(orders, orderId)
trades = None
if cachedOrder is not None:
trades = self.safe_value(order, 'trades')
if lastTrade is not None:
if trades is None:
trades = []
trades.append(lastTrade)
return self.safe_order({
'info': order,
'symbol': symbol,
'id': orderId,
'clientOrderId': self.safe_string(orderInfo, 'client_order_id'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': updatedTimestamp,
'type': self.safe_string(orderInfo, 'type'),
'timeInForce': None,
'postOnly': None,
'side': self.parse_ws_order_side(self.safe_string(orderInfo, 'side')),
'price': self.safe_string(orderInfo, 'price'),
'stopPrice': None,
'triggerPrice': None,
'amount': self.safe_string(orderInfo, 'size'),
'cost': None,
'average': self.safe_string(orderInfo, 'deal_avg_price'),
'filled': self.safe_string(orderInfo, 'deal_size'),
'remaining': None,
'status': self.parse_ws_order_status(self.safe_string(order, 'action')),
'fee': None,
'trades': trades,
}, market)
def parse_ws_order_status(self, statusId):
statuses: dict = {
'1': 'closed', # match deal
'2': 'open', # submit order
'3': 'canceled', # cancel order
'4': 'closed', # liquidate cancel order
'5': 'canceled', # adl cancel order
'6': 'open', # part liquidate
'7': 'open', # bankrupty order
'8': 'closed', # passive adl match deal
'9': 'closed', # active adl match deal
}
return self.safe_string(statuses, statusId, statusId)
def parse_ws_order_side(self, sideId):
sides: dict = {
'1': 'buy', # buy_open_long
'2': 'buy', # buy_close_short
'3': 'sell', # sell_close_long
'4': 'sell', # sell_open_short
}
return self.safe_string(sides, sideId, sideId)
async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
"""
https://developer-pro.bitmart.com/en/futures/#private-position-channel
watch all open positions
:param str[]|None symbols: list of unified market symbols
:param int [since]: the earliest time in ms to fetch positions
:param int [limit]: the maximum number of positions to retrieve
:param dict params: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
"""
await self.load_markets()
type = 'swap'
await self.authenticate(type, params)
symbols = self.market_symbols(symbols, 'swap', True, True, False)
messageHash = 'positions'
if symbols is not None:
messageHash += '::' + ','.join(symbols)
subscriptionHash = 'futures/position'
request: dict = {
'action': 'subscribe',
'args': ['futures/position'],
}
url = self.implode_hostname(self.urls['api']['ws'][type]['private'])
newPositions = await self.watch(url, messageHash, self.deep_extend(request, params), subscriptionHash)
if self.newUpdates:
return newPositions
return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit)
def handle_positions(self, client: Client, message):
#
# {
# "group":"futures/position",
# "data":[
# {
# "symbol":"LTCUSDT",
# "hold_volume":"5",
# "position_type":2,
# "open_type":2,
# "frozen_volume":"0",
# "close_volume":"0",
# "hold_avg_price":"71.582",
# "close_avg_price":"0",
# "open_avg_price":"71.582",
# "liquidate_price":"0",
# "create_time":1701623327513,
# "update_time":1701627620439
# },
# {
# "symbol":"LTCUSDT",
# "hold_volume":"6",
# "position_type":1,
# "open_type":2,
# "frozen_volume":"0",
# "close_volume":"0",
# "hold_avg_price":"71.681666666666666667",
# "close_avg_price":"0",
# "open_avg_price":"71.681666666666666667",
# "liquidate_price":"0",
# "create_time":1701621167225,
# "update_time":1701628152614
# }
# ]
# }
#
data = self.safe_value(message, 'data', [])
if self.positions is None:
self.positions = ArrayCacheBySymbolBySide()
cache = self.positions
newPositions = []
for i in range(0, len(data)):
rawPosition = data[i]
position = self.parse_ws_position(rawPosition)
newPositions.append(position)
cache.append(position)
messageHashes = self.find_message_hashes(client, '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, 'positions')
def parse_ws_position(self, position, market: Market = None):
#
# {
# "symbol":"LTCUSDT",
# "hold_volume":"6",
# "position_type":1,
# "open_type":2,
# "frozen_volume":"0",
# "close_volume":"0",
# "hold_avg_price":"71.681666666666666667",
# "close_avg_price":"0",
# "open_avg_price":"71.681666666666666667",
# "liquidate_price":"0",
# "create_time":1701621167225,
# "update_time":1701628152614
# }
#
marketId = self.safe_string(position, 'symbol')
market = self.safe_market(marketId, market, None, 'swap')
symbol = market['symbol']
openTimestamp = self.safe_integer(position, 'create_time')
timestamp = self.safe_integer(position, 'update_time')
side = self.safe_integer(position, 'position_type')
marginModeId = self.safe_integer(position, 'open_type')
return self.safe_position({
'info': position,
'id': None,
'symbol': symbol,
'timestamp': openTimestamp,
'datetime': self.iso8601(openTimestamp),
'lastUpdateTimestamp': timestamp,
'hedged': None,
'side': 'long' if (side == 1) else 'short',
'contracts': self.safe_number(position, 'hold_volume'),
'contractSize': self.safe_number(market, 'contractSize'),
'entryPrice': self.safe_number(position, 'open_avg_price'),
'markPrice': self.safe_number(position, 'hold_avg_price'),
'lastPrice': None,
'notional': None,
'leverage': None,
'collateral': None,
'initialMargin': None,
'initialMarginPercentage': None,
'maintenanceMargin': None,
'maintenanceMarginPercentage': None,
'unrealizedPnl': None,
'realizedPnl': None,
'liquidationPrice': self.safe_number(position, 'liquidate_price'),
'marginMode': 'isolated' if (marginModeId == 1) else 'cross',
'percentage': None,
'marginRatio': None,
'stopLossPrice': None,
'takeProfitPrice': None,
})
def handle_trade(self, client: Client, message):
#
# spot
# {
# "table": "spot/trade",
# "data": [
# {
# "price": "52700.50",
# "s_t": 1630982050,
# "side": "buy",
# "size": "0.00112",
# "symbol": "BTC_USDT"
# },
# ]
# }
#
# swap
# {
# "group":"futures/trade:BTCUSDT",
# "data":[
# {
# "trade_id":6798697637,
# "symbol":"BTCUSDT",
# "deal_price":"39735.8",
# "deal_vol":"2",
# "way":1,
# "created_at":"2023-12-03T15:48:23.517518538Z",
# "m": True,
# }
# ]
# }
#
data = self.safe_value(message, 'data')
if data is None:
return
symbol = None
length = len(data)
isSwap = ('group' in message)
if isSwap:
# in swap, chronologically decreasing: 1709536849322, 1709536848954,
for i in range(0, length):
index = length - i - 1
symbol = self.handle_trade_loop(data[index])
else:
# in spot, chronologically increasing: 1709536771200, 1709536771226,
for i in range(0, length):
symbol = self.handle_trade_loop(data[i])
client.resolve(self.trades[symbol], 'trade:' + symbol)
def handle_trade_loop(self, entry):
trade = self.parse_ws_trade(entry)
symbol = trade['symbol']
tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
if self.safe_value(self.trades, symbol) is None:
self.trades[symbol] = ArrayCache(tradesLimit)
stored = self.trades[symbol]
stored.append(trade)
return symbol
def parse_ws_trade(self, trade: dict, market: Market = None):
#
# spot
# {
# "ms_t": 1740320841473,
# "price": "2806.54",
# "s_t": 1740320841,
# "side": "sell",
# "size": "0.77598",
# "symbol": "ETH_USDT"
# }
#
# swap
# {
# "trade_id": "3000000245258661",
# "symbol": "ETHUSDT",
# "deal_price": "2811.1",
# "deal_vol": "1858",
# "way": 2,
# "m": True,
# "created_at": "2025-02-23T13:59:59.646490751Z"
# }
#
marketId = self.safe_string(trade, 'symbol')
market = self.safe_market(marketId, market)
timestamp = self.safe_integer(trade, 'ms_t')
datetime: Str = None
if timestamp is None:
datetime = self.safe_string(trade, 'created_at')
timestamp = self.parse8601(datetime)
else:
datetime = self.iso8601(timestamp)
takerOrMaker = None # True for public trades
side = self.safe_string(trade, 'side')
buyerMaker = self.safe_bool(trade, 'm')
if buyerMaker is not None:
if side is None:
if buyerMaker:
side = 'sell'
else:
side = 'buy'
takerOrMaker = 'taker'
return self.safe_trade({
'info': trade,
'id': self.safe_string(trade, 'trade_id'),
'order': None,
'timestamp': timestamp,
'datetime': datetime,
'symbol': market['symbol'],
'type': None,
'side': side,
'price': self.safe_string_2(trade, 'price', 'deal_price'),
'amount': self.safe_string_2(trade, 'size', 'deal_vol'),
'cost': None,
'takerOrMaker': takerOrMaker,
'fee': None,
}, market)
def handle_ticker(self, client: Client, message):
#
# {
# "data": [
# {
# "base_volume_24h": "78615593.81",
# "high_24h": "52756.97",
# "last_price": "52638.31",
# "low_24h": "50991.35",
# "open_24h": "51692.03",
# "s_t": 1630981727,
# "symbol": "BTC_USDT"
# }
# ],
# "table": "spot/ticker"
# }
#
# {
# "data": {
# "symbol": "ETHUSDT",
# "last_price": "2807.73",
# "volume_24": "2227011952",
# "range": "0.0273398194664491",
# "mark_price": "2807.5",
# "index_price": "2808.71047619",
# "ask_price": "2808.04",
# "ask_vol": "7371",
# "bid_price": "2807.28",
# "bid_vol": "3561"
# },
# "group": "futures/ticker:ETHUSDT@100ms"
# }
#
self.handle_bid_ask(client, message)
table = self.safe_string(message, 'table')
isSpot = (table is not None)
rawTickers = []
if isSpot:
rawTickers = self.safe_list(message, 'data', [])
else:
rawTickers = [self.safe_value(message, 'data', {})]
if not len(rawTickers):
return
for i in range(0, len(rawTickers)):
ticker = self.parse_ticker(rawTickers[i]) if isSpot else self.parse_ws_swap_ticker(rawTickers[i])
symbol = ticker['symbol']
self.tickers[symbol] = ticker
messageHash = 'ticker:' + symbol
client.resolve(ticker, messageHash)
def parse_ws_swap_ticker(self, ticker, market: Market = None):
#
# {
# "symbol": "ETHUSDT",
# "last_price": "2807.73",
# "volume_24": "2227011952",
# "range": "0.0273398194664491",
# "mark_price": "2807.5",
# "index_price": "2808.71047619",
# "ask_price": "2808.04",
# "ask_vol": "7371",
# "bid_price": "2807.28",
# "bid_vol": "3561"
# }
#
marketId = self.safe_string(ticker, 'symbol')
return self.safe_ticker({
'symbol': self.safe_symbol(marketId, market, '', 'swap'),
'timestamp': None,
'datetime': None,
'high': None,
'low': None,
'bid': self.safe_string(ticker, 'bid_price'),
'bidVolume': self.safe_string(ticker, 'bid_vol'),
'ask': self.safe_string(ticker, 'ask_price'),
'askVolume': self.safe_string(ticker, 'ask_vol'),
'vwap': None,
'open': None,
'close': None,
'last': self.safe_string(ticker, 'last_price'),
'previousClose': None,
'change': None,
'percentage': None,
'average': None,
'baseVolume': None,
'quoteVolume': self.safe_string(ticker, 'volume_24'),
'info': ticker,
'markPrice': self.safe_string(ticker, 'mark_price'),
'indexPrice': self.safe_string(ticker, 'index_price'),
}, market)
async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
"""
https://developer-pro.bitmart.com/en/spot/#public-kline-channel
https://developer-pro.bitmart.com/en/futuresv2/#public-klinebin-channel
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 [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()
symbol = self.symbol(symbol)
market = self.market(symbol)
type = 'spot'
type, params = self.handle_market_type_and_params('watchOrderBook', market, params)
timeframes = self.safe_value(self.options, 'timeframes', {})
interval = self.safe_string(timeframes, timeframe)
name = None
if type == 'spot':
name = 'kline' + interval
else:
name = 'klineBin' + interval
ohlcv = await self.subscribe(name, symbol, type, 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):
#
# {
# "data": [
# {
# "candle": [
# 1631056350,
# "46532.83",
# "46555.71",
# "46511.41",
# "46555.71",
# "0.25"
# ],
# "symbol": "BTC_USDT"
# }
# ],
# "table": "spot/kline1m"
# }
# swap
# {
# "group":"futures/klineBin1m:BTCUSDT",
# "data":{
# "symbol":"BTCUSDT",
# "items":[
# {
# "o":"39635.8",
# "h":"39636",
# "l":"39614.4",
# "c":"39629.7",
# "v":"31852",
# "ts":1701617761
# }
# ]
# }
# }
#
channel = self.safe_string_2(message, 'table', 'group')
isSpot = (channel.find('spot') >= 0)
data = self.safe_value(message, 'data')
if data is None:
return
parts = channel.split('/')
part1 = self.safe_string(parts, 1, '')
interval = part1.replace('kline', '')
interval = interval.replace('Bin', '')
intervalParts = interval.split(':')
interval = self.safe_string(intervalParts, 0)
# use a reverse lookup in a static map instead
timeframes = self.safe_value(self.options, 'timeframes', {})
timeframe = self.find_timeframe(interval, timeframes)
duration = self.parse_timeframe(timeframe)
durationInMs = duration * 1000
if isSpot:
for i in range(0, len(data)):
marketId = self.safe_string(data[i], 'symbol')
market = self.safe_market(marketId)
symbol = market['symbol']
rawOHLCV = self.safe_value(data[i], 'candle')
parsed = self.parse_ohlcv(rawOHLCV, market)
parsed[0] = self.parse_to_int(parsed[0] / durationInMs) * durationInMs
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
stored.append(parsed)
messageHash = channel + ':' + marketId
client.resolve(stored, messageHash)
else:
marketId = self.safe_string(data, 'symbol')
market = self.safe_market(marketId, None, None, 'swap')
symbol = market['symbol']
items = self.safe_value(data, 'items', [])
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
for i in range(0, len(items)):
candle = items[i]
parsed = self.parse_ohlcv(candle, market)
stored.append(parsed)
client.resolve(stored, channel)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
https://developer-pro.bitmart.com/en/spot/#public-depth-all-channel
https://developer-pro.bitmart.com/en/spot/#public-depth-increase-channel
https://developer-pro.bitmart.com/en/futuresv2/#public-depth-channel
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
:param str symbol: unified symbol of the market to fetch the order book for
:param int [limit]: the maximum amount of order book entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.speed]: *futures only* '100ms' or '200ms'
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
await self.load_markets()
options = self.safe_value(self.options, 'watchOrderBook', {})
depth = self.safe_string(options, 'depth', 'depth/increase100')
symbol = self.symbol(symbol)
market = self.market(symbol)
type = 'spot'
type, params = self.handle_market_type_and_params('watchOrderBook', market, params)
if type == 'swap' and depth == 'depth/increase100':
depth = 'depth50'
orderbook = await self.subscribe(depth, symbol, type, params)
return orderbook.limit()
def handle_delta(self, bookside, delta):
price = self.safe_float(delta, 0)
amount = self.safe_float(delta, 1)
bookside.store(price, amount)
def handle_deltas(self, bookside, deltas):
for i in range(0, len(deltas)):
self.handle_delta(bookside, deltas[i])
def handle_order_book_message(self, client: Client, message, orderbook):
#
# {
# "asks": [
# ['46828.38', "0.21847"],
# ['46830.68', "0.08232"],
# ['46832.08', "0.09285"],
# ['46837.82', "0.02028"],
# ['46839.43', "0.15068"]
# ],
# "bids": [
# ['46820.78', "0.00444"],
# ['46814.33', "0.00234"],
# ['46813.50', "0.05021"],
# ['46808.14', "0.00217"],
# ['46808.04', "0.00013"]
# ],
# "ms_t": 1631044962431,
# "symbol": "BTC_USDT"
# }
#
asks = self.safe_list(message, 'asks', [])
bids = self.safe_list(message, 'bids', [])
self.handle_deltas(orderbook['asks'], asks)
self.handle_deltas(orderbook['bids'], bids)
timestamp = self.safe_integer(message, 'ms_t')
marketId = self.safe_string(message, 'symbol')
symbol = self.safe_symbol(marketId)
orderbook['symbol'] = symbol
orderbook['timestamp'] = timestamp
orderbook['datetime'] = self.iso8601(timestamp)
return orderbook
def handle_order_book(self, client: Client, message):
#
# spot depth-all
#
# {
# "data": [
# {
# "asks": [
# ['46828.38', "0.21847"],
# ['46830.68', "0.08232"],
# ...
# ],
# "bids": [
# ['46820.78', "0.00444"],
# ['46814.33', "0.00234"],
# ...
# ],
# "ms_t": 1631044962431,
# "symbol": "BTC_USDT"
# }
# ],
# "table": "spot/depth5"
# }
#
# spot increse depth snapshot
#
# {
# "data":[
# {
# "asks":[
# ["43652.52", "0.02039"],
# ...
# ],
# "bids":[
# ["43652.51", "0.00500"],
# ...
# ],
# "ms_t":1703376836487,
# "symbol":"BTC_USDT",
# "type":"snapshot", # or update
# "version":2141731
# }
# ],
# "table":"spot/depth/increase100"
# }
#
# swap
#
# {
# "group":"futures/depth50:BTCUSDT",
# "data":{
# "symbol":"BTCUSDT",
# "way":1,
# "depths":[
# {
# "price":"39509.8",
# "vol":"2379"
# },
# {
# "price":"39509.6",
# "vol":"6815"
# },
# ...
# ],
# "ms_t":1701566021194
# }
# }
#
isSpot = ('table' in message)
datas = []
if isSpot:
datas = self.safe_list(message, 'data', datas)
else:
orderBookEntry = self.safe_dict(message, 'data')
if orderBookEntry is not None:
datas.append(orderBookEntry)
length = len(datas)
if length <= 0:
return
channelName = self.safe_string_2(message, 'table', 'group')
# find limit subscribed to
limitsToCheck = ['100', '50', '20', '10', '5']
limit = 0
for i in range(0, len(limitsToCheck)):
limitString = limitsToCheck[i]
if channelName.find(limitString) >= 0:
limit = self.parse_to_int(limitString)
break
if isSpot:
channel = channelName.replace('spot/', '')
for i in range(0, len(datas)):
update = datas[i]
marketId = self.safe_string(update, 'symbol')
symbol = self.safe_symbol(marketId)
if not (symbol in self.orderbooks):
ob = self.order_book({}, limit)
ob['symbol'] = symbol
self.orderbooks[symbol] = ob
orderbook = self.orderbooks[symbol]
type = self.safe_string(update, 'type')
if (type == 'snapshot') or (not(channelName.find('increase') >= 0)):
orderbook.reset({})
self.handle_order_book_message(client, update, orderbook)
timestamp = self.safe_integer(update, 'ms_t')
if orderbook['timestamp'] is None:
orderbook['timestamp'] = timestamp
orderbook['datetime'] = self.iso8601(timestamp)
messageHash = channelName + ':' + marketId
client.resolve(orderbook, messageHash)
# resolve ForSymbols
messageHashForMulti = channel + ':' + symbol
client.resolve(orderbook, messageHashForMulti)
else:
tableParts = channelName.split(':')
channel = tableParts[0].replace('futures/', '')
data = datas[0] # contract markets always contain only one member
depths = data['depths']
marketId = self.safe_string(data, 'symbol')
symbol = self.safe_symbol(marketId)
if not (symbol in self.orderbooks):
ob = self.order_book({}, limit)
ob['symbol'] = symbol
self.orderbooks[symbol] = ob
orderbook = self.orderbooks[symbol]
way = self.safe_integer(data, 'way')
side = 'bids' if (way == 1) else 'asks'
if way == 1:
orderbook[side] = Bids([], limit)
else:
orderbook[side] = Asks([], limit)
for i in range(0, len(depths)):
depth = depths[i]
price = self.safe_number(depth, 'price')
amount = self.safe_number(depth, 'vol')
orderbookSide = self.safe_value(orderbook, side)
orderbookSide.store(price, amount)
bidsLength = len(orderbook['bids'])
asksLength = len(orderbook['asks'])
if (bidsLength == 0) or (asksLength == 0):
return
timestamp = self.safe_integer(data, 'ms_t')
orderbook['timestamp'] = timestamp
orderbook['datetime'] = self.iso8601(timestamp)
messageHash = channelName
client.resolve(orderbook, messageHash)
# resolve ForSymbols
messageHashForMulti = channel + ':' + symbol
client.resolve(orderbook, messageHashForMulti)
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
https://developer-pro.bitmart.com/en/spot/#public-depth-increase-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
:param str [params.depth]: the type of order book to subscribe to, default is 'depth/increase100', also accepts 'depth5' or 'depth20' or depth50
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
await self.load_markets()
type = None
symbols, type, params = self.get_params_for_multiple_sub('watchOrderBookForSymbols', symbols, limit, params)
channel = None
channel, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'depth', 'depth/increase100')
if type == 'swap' and channel == 'depth/increase100':
channel = 'depth50'
orderbook = await self.subscribe_multiple(channel, type, symbols, params)
return orderbook.limit()
async def authenticate(self, type, params={}):
self.check_required_credentials()
url = self.implode_hostname(self.urls['api']['ws'][type]['private'])
messageHash = 'authenticated'
client = self.client(url)
future = client.reusableFuture(messageHash)
authenticated = self.safe_value(client.subscriptions, messageHash)
if authenticated is None:
timestamp = str(self.milliseconds())
memo = self.uid
path = 'bitmart.WebSocket'
auth = timestamp + '#' + memo + '#' + path
signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
request = None
if type == 'spot':
request = {
'op': 'login',
'args': [
self.apiKey,
timestamp,
signature,
],
}
else:
request = {
'action': 'access',
'args': [
self.apiKey,
timestamp,
signature,
'web',
],
}
message = self.extend(request, params)
self.watch(url, messageHash, message, messageHash)
return await future
def handle_subscription_status(self, client: Client, message):
#
# {"event":"subscribe","channel":"spot/depth:BTC-USDT"}
#
return message
def handle_authenticate(self, client: Client, message):
#
# spot
# {event: "login"}
# swap
# {action: 'access', success: True}
#
messageHash = 'authenticated'
future = self.safe_value(client.futures, messageHash)
future.resolve(True)
def handle_error_message(self, client: Client, message) -> Bool:
#
# {event: "error", message: "Invalid sign", errorCode: 30013}
# {"event":"error","message":"Unrecognized request: {\"event\":\"subscribe\",\"channel\":\"spot/depth:BTC-USDT\"}","errorCode":30039}
# {
# action: '',
# group: 'futures/trade:BTCUSDT',
# success: False,
# request: {action: '', args: ['futures/trade:BTCUSDT']},
# error: 'Invalid action [] for group [futures/trade:BTCUSDT]'
# }
#
errorCode = self.safe_string(message, 'errorCode')
error = self.safe_string(message, 'error')
try:
if errorCode is not None or error is not None:
feedback = self.id + ' ' + self.json(message)
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
messageString = self.safe_value(message, 'message', error)
self.throw_broadly_matched_exception(self.exceptions['broad'], messageString, feedback)
action = self.safe_string(message, 'action')
if action == 'access':
raise AuthenticationError(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]
client.reject(e)
return True
def handle_message(self, client: Client, message):
if self.handle_error_message(client, message):
return
#
# {"event":"error","message":"Unrecognized request: {\"event\":\"subscribe\",\"channel\":\"spot/depth:BTC-USDT\"}","errorCode":30039}
#
# subscribe events on spot:
#
# {"event":"subscribe", "topic":"spot/kline1m:BTC_USDT"}
#
# subscribe on contracts:
#
# {"action":"subscribe", "group":"futures/klineBin1m:BTCUSDT", "success":true, "request":{"action":"subscribe", "args":["futures/klineBin1m:BTCUSDT"]}}
#
# regular updates - spot
#
# {
# "table": "spot/depth",
# "action": "partial",
# "data": [
# {
# "instrument_id": "BTC-USDT",
# "asks": [
# ["5301.8", "0.03763319", "1"],
# ["5302.4", "0.00305", "2"],
# ],
# "bids": [
# ["5301.7", "0.58911427", "6"],
# ["5301.6", "0.01222922", "4"],
# ],
# "timestamp": "2020-03-16T03:25:00.440Z",
# "checksum": -2088736623
# }
# ]
# }
#
# regular updates - contracts
#
# {
# group: "futures/klineBin1m:BTCUSDT",
# data: {
# symbol: "BTCUSDT",
# items: [{o: "67944.7", "h": ....}],
# },
# }
#
# {data: '', table: "spot/user/order"}
#
# the only realiable way(for both spot & swap) is to check 'data' key
isDataUpdate = ('data' in message)
if not isDataUpdate:
event = self.safe_string_2(message, 'event', 'action')
if event is not None:
methods: dict = {
# 'info': self.handleSystemStatus,
'login': self.handle_authenticate,
'access': self.handle_authenticate,
'subscribe': self.handle_subscription_status,
}
method = self.safe_value(methods, event)
if method is not None:
method(client, message)
else:
channel = self.safe_string_2(message, 'table', 'group')
methods: dict = {
'depth': self.handle_order_book,
'ticker': self.handle_ticker,
'trade': self.handle_trade,
'kline': self.handle_ohlcv,
'order': self.handle_orders,
'position': self.handle_positions,
'balance': self.handle_balance,
'asset': self.handle_balance,
}
keys = list(methods.keys())
for i in range(0, len(keys)):
key = keys[i]
if channel.find(key) >= 0:
method = self.safe_value(methods, key)
method(client, message)