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

1695 lines
73 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
import hashlib
from ccxt.base.types import Any, Balances, Bool, Int, Liquidation, 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 RateLimitExceeded
class bitmex(ccxt.async_support.bitmex):
def describe(self) -> Any:
return self.deep_extend(super(bitmex, self).describe(), {
'has': {
'ws': True,
'watchBalance': True,
'watchLiquidations': True,
'watchLiquidationsForSymbols': True,
'watchMyLiquidations': None,
'watchMyLiquidationsForSymbols': None,
'watchMyTrades': True,
'watchOHLCV': True,
'watchOrderBook': True,
'watchOrderBookForSymbols': True,
'watchOrders': True,
'watchPostions': True,
'watchTicker': True,
'watchTickers': True,
'watchTrades': True,
'watchTradesForSymbols': True,
},
'urls': {
'test': {
'ws': 'wss://ws.testnet.bitmex.com/realtime',
},
'api': {
'ws': 'wss://ws.bitmex.com/realtime',
},
},
# 'versions': {
# 'ws': '0.2.0',
# },
'options': {
'watchOrderBookLevel': 'orderBookL2', # 'orderBookL2' = L2 full order book, 'orderBookL2_25' = L2 top 25, 'orderBook10' L3 top 10
'tradesLimit': 1000,
'OHLCVLimit': 1000,
},
'exceptions': {
'ws': {
'exact': {
},
'broad': {
'Rate limit exceeded': RateLimitExceeded,
},
},
},
})
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://www.bitmex.com/app/wsAPI#Subscriptions
: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:
"""
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
https://www.bitmex.com/app/wsAPI#Subscriptions
: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, True)
name = 'instrument'
url = self.urls['api']['ws']
messageHashes = []
rawSubscriptions = []
if symbols is not None:
for i in range(0, len(symbols)):
symbol = symbols[i]
market = self.market(symbol)
subscription = name + ':' + market['id']
rawSubscriptions.append(subscription)
messageHash = 'ticker:' + symbol
messageHashes.append(messageHash)
else:
rawSubscriptions.append(name)
messageHashes.append('alltickers')
request: dict = {
'op': 'subscribe',
'args': rawSubscriptions,
}
ticker = await self.watch_multiple(url, messageHashes, self.extend(request, params), rawSubscriptions)
if self.newUpdates:
result: dict = {}
result[ticker['symbol']] = ticker
return result
return self.filter_by_array(self.tickers, 'symbol', symbols)
def handle_ticker(self, client: Client, message):
#
# {
# "table": "instrument",
# "action": "partial",
# "keys": ["symbol"],
# "types": {
# "symbol": "symbol",
# "rootSymbol": "symbol",
# "state": "symbol",
# "typ": "symbol",
# "listing": "timestamp",
# "front": "timestamp",
# "expiry": "timestamp",
# "settle": "timestamp",
# "relistInterval": "timespan",
# "inverseLeg": "symbol",
# "sellLeg": "symbol",
# "buyLeg": "symbol",
# "optionStrikePcnt": "float",
# "optionStrikeRound": "float",
# "optionStrikePrice": "float",
# "optionMultiplier": "float",
# "positionCurrency": "symbol",
# "underlying": "symbol",
# "quoteCurrency": "symbol",
# "underlyingSymbol": "symbol",
# "reference": "symbol",
# "referenceSymbol": "symbol",
# "calcInterval": "timespan",
# "publishInterval": "timespan",
# "publishTime": "timespan",
# "maxOrderQty": "long",
# "maxPrice": "float",
# "lotSize": "long",
# "tickSize": "float",
# "multiplier": "long",
# "settlCurrency": "symbol",
# "underlyingToPositionMultiplier": "long",
# "underlyingToSettleMultiplier": "long",
# "quoteToSettleMultiplier": "long",
# "isQuanto": "boolean",
# "isInverse": "boolean",
# "initMargin": "float",
# "maintMargin": "float",
# "riskLimit": "long",
# "riskStep": "long",
# "limit": "float",
# "capped": "boolean",
# "taxed": "boolean",
# "deleverage": "boolean",
# "makerFee": "float",
# "takerFee": "float",
# "settlementFee": "float",
# "insuranceFee": "float",
# "fundingBaseSymbol": "symbol",
# "fundingQuoteSymbol": "symbol",
# "fundingPremiumSymbol": "symbol",
# "fundingTimestamp": "timestamp",
# "fundingInterval": "timespan",
# "fundingRate": "float",
# "indicativeFundingRate": "float",
# "rebalanceTimestamp": "timestamp",
# "rebalanceInterval": "timespan",
# "openingTimestamp": "timestamp",
# "closingTimestamp": "timestamp",
# "sessionInterval": "timespan",
# "prevClosePrice": "float",
# "limitDownPrice": "float",
# "limitUpPrice": "float",
# "bankruptLimitDownPrice": "float",
# "bankruptLimitUpPrice": "float",
# "prevTotalVolume": "long",
# "totalVolume": "long",
# "volume": "long",
# "volume24h": "long",
# "prevTotalTurnover": "long",
# "totalTurnover": "long",
# "turnover": "long",
# "turnover24h": "long",
# "homeNotional24h": "float",
# "foreignNotional24h": "float",
# "prevPrice24h": "float",
# "vwap": "float",
# "highPrice": "float",
# "lowPrice": "float",
# "lastPrice": "float",
# "lastPriceProtected": "float",
# "lastTickDirection": "symbol",
# "lastChangePcnt": "float",
# "bidPrice": "float",
# "midPrice": "float",
# "askPrice": "float",
# "impactBidPrice": "float",
# "impactMidPrice": "float",
# "impactAskPrice": "float",
# "hasLiquidity": "boolean",
# "openInterest": "long",
# "openValue": "long",
# "fairMethod": "symbol",
# "fairBasisRate": "float",
# "fairBasis": "float",
# "fairPrice": "float",
# "markMethod": "symbol",
# "markPrice": "float",
# "indicativeTaxRate": "float",
# "indicativeSettlePrice": "float",
# "optionUnderlyingPrice": "float",
# "settledPrice": "float",
# "timestamp": "timestamp"
# },
# "foreignKeys": {
# "inverseLeg": "instrument",
# "sellLeg": "instrument",
# "buyLeg": "instrument"
# },
# "attributes": {symbol: "unique"},
# "filter": {symbol: "XBTUSD"},
# "data": [
# {
# "symbol": "XBTUSD",
# "rootSymbol": "XBT",
# "state": "Open",
# "typ": "FFWCSX",
# "listing": "2016-05-13T12:00:00.000Z",
# "front": "2016-05-13T12:00:00.000Z",
# "expiry": null,
# "settle": null,
# "relistInterval": null,
# "inverseLeg": '',
# "sellLeg": '',
# "buyLeg": '',
# "optionStrikePcnt": null,
# "optionStrikeRound": null,
# "optionStrikePrice": null,
# "optionMultiplier": null,
# "positionCurrency": "USD",
# "underlying": "XBT",
# "quoteCurrency": "USD",
# "underlyingSymbol": "XBT=",
# "reference": "BMEX",
# "referenceSymbol": ".BXBT",
# "calcInterval": null,
# "publishInterval": null,
# "publishTime": null,
# "maxOrderQty": 10000000,
# "maxPrice": 1000000,
# "lotSize": 1,
# "tickSize": 0.5,
# "multiplier": -100000000,
# "settlCurrency": "XBt",
# "underlyingToPositionMultiplier": null,
# "underlyingToSettleMultiplier": -100000000,
# "quoteToSettleMultiplier": null,
# "isQuanto": False,
# "isInverse": True,
# "initMargin": 0.01,
# "maintMargin": 0.005,
# "riskLimit": 20000000000,
# "riskStep": 10000000000,
# "limit": null,
# "capped": False,
# "taxed": True,
# "deleverage": True,
# "makerFee": -0.00025,
# "takerFee": 0.00075,
# "settlementFee": 0,
# "insuranceFee": 0,
# "fundingBaseSymbol": ".XBTBON8H",
# "fundingQuoteSymbol": ".USDBON8H",
# "fundingPremiumSymbol": ".XBTUSDPI8H",
# "fundingTimestamp": "2020-01-29T12:00:00.000Z",
# "fundingInterval": "2000-01-01T08:00:00.000Z",
# "fundingRate": 0.000597,
# "indicativeFundingRate": 0.000652,
# "rebalanceTimestamp": null,
# "rebalanceInterval": null,
# "openingTimestamp": "2020-01-29T11:00:00.000Z",
# "closingTimestamp": "2020-01-29T12:00:00.000Z",
# "sessionInterval": "2000-01-01T01:00:00.000Z",
# "prevClosePrice": 9063.96,
# "limitDownPrice": null,
# "limitUpPrice": null,
# "bankruptLimitDownPrice": null,
# "bankruptLimitUpPrice": null,
# "prevTotalVolume": 1989881049026,
# "totalVolume": 1990196740950,
# "volume": 315691924,
# "volume24h": 4491824765,
# "prevTotalTurnover": 27865497128425564,
# "totalTurnover": 27868891594857150,
# "turnover": 3394466431587,
# "turnover24h": 48863390064843,
# "homeNotional24h": 488633.9006484273,
# "foreignNotional24h": 4491824765,
# "prevPrice24h": 9091,
# "vwap": 9192.8663,
# "highPrice": 9440,
# "lowPrice": 8886,
# "lastPrice": 9287,
# "lastPriceProtected": 9287,
# "lastTickDirection": "PlusTick",
# "lastChangePcnt": 0.0216,
# "bidPrice": 9286,
# "midPrice": 9286.25,
# "askPrice": 9286.5,
# "impactBidPrice": 9285.9133,
# "impactMidPrice": 9286.75,
# "impactAskPrice": 9287.6382,
# "hasLiquidity": True,
# "openInterest": 967826984,
# "openValue": 10432207060536,
# "fairMethod": "FundingRate",
# "fairBasisRate": 0.6537149999999999,
# "fairBasis": 0.33,
# "fairPrice": 9277.2,
# "markMethod": "FairPrice",
# "markPrice": 9277.2,
# "indicativeTaxRate": 0,
# "indicativeSettlePrice": 9276.87,
# "optionUnderlyingPrice": null,
# "settledPrice": null,
# "timestamp": "2020-01-29T11:31:37.114Z"
# }
# ]
# }
#
data = self.safe_list(message, 'data', [])
tickers: dict = {}
for i in range(0, len(data)):
update = data[i]
marketId = self.safe_string(update, 'symbol')
symbol = self.safe_symbol(marketId)
if not (symbol in self.tickers):
self.tickers[symbol] = self.parse_ticker({})
updatedTicker = self.parse_ticker(update)
fullParsedTicker = self.deep_extend(self.tickers[symbol], updatedTicker)
tickers[symbol] = fullParsedTicker
self.tickers[symbol] = fullParsedTicker
messageHash = 'ticker:' + symbol
client.resolve(fullParsedTicker, messageHash)
client.resolve(fullParsedTicker, 'alltickers')
return message
async def watch_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Liquidation]:
"""
watch the public liquidations of a trading pair
https://www.bitmex.com/app/wsAPI#Liquidation
:param str symbol: unified CCXT market symbol
:param int [since]: the earliest time in ms to fetch liquidations for
:param int [limit]: the maximum number of liquidation structures to retrieve
:param dict [params]: exchange specific parameters for the bitmex api endpoint
:returns dict: an array of `liquidation structures <https://github.com/ccxt/ccxt/wiki/Manual#liquidation-structure>`
"""
return self.watch_liquidations_for_symbols([symbol], since, limit, params)
async def watch_liquidations_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Liquidation]:
"""
watch the public liquidations of a trading pair
https://www.bitmex.com/app/wsAPI#Liquidation
:param str[] symbols:
:param int [since]: the earliest time in ms to fetch liquidations for
:param int [limit]: the maximum number of liquidation structures to retrieve
:param dict [params]: exchange specific parameters for the bitmex api endpoint
:returns dict: an array of `liquidation structures <https://github.com/ccxt/ccxt/wiki/Manual#liquidation-structure>`
"""
await self.load_markets()
symbols = self.market_symbols(symbols, None, True, True)
messageHashes = []
subscriptionHashes = []
if self.is_empty(symbols):
subscriptionHashes.append('liquidation')
messageHashes.append('liquidations')
else:
for i in range(0, len(symbols)):
symbol = symbols[i]
market = self.market(symbol)
subscriptionHashes.append('liquidation:' + market['id'])
messageHashes.append('liquidations::' + symbol)
url = self.urls['api']['ws']
request = {
'op': 'subscribe',
'args': subscriptionHashes,
}
newLiquidations = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), subscriptionHashes)
if self.newUpdates:
return newLiquidations
return self.filter_by_symbols_since_limit(self.liquidations, symbols, since, limit, True)
def handle_liquidation(self, client: Client, message):
#
# {
# "table":"liquidation",
# "action":"partial",
# "keys":[
# "orderID"
# ],
# "types":{
# "orderID":"guid",
# "symbol":"symbol",
# "side":"symbol",
# "price":"float",
# "leavesQty":"long"
# },
# "filter":{},
# "data":[
# {
# "orderID":"e0a568ee-7830-4428-92c3-73e82b9576ce",
# "symbol":"XPLAUSDT",
# "side":"Sell",
# "price":0.206,
# "leavesQty":340
# }
# ]
# }
#
rawLiquidations = self.safe_value(message, 'data', [])
newLiquidations = []
for i in range(0, len(rawLiquidations)):
rawLiquidation = rawLiquidations[i]
liquidation = self.parse_liquidation(rawLiquidation)
symbol = liquidation['symbol']
liquidations = self.safe_value(self.liquidations, symbol)
if liquidations is None:
limit = self.safe_integer(self.options, 'liquidationsLimit', 1000)
liquidations = ArrayCache(limit)
liquidations.append(liquidation)
self.liquidations[symbol] = liquidations
newLiquidations.append(liquidation)
client.resolve(newLiquidations, 'liquidations')
liquidationsBySymbol = self.index_by(newLiquidations, 'symbol')
symbols = list(liquidationsBySymbol.keys())
for i in range(0, len(symbols)):
symbol = symbols[i]
client.resolve(liquidationsBySymbol[symbol], 'liquidations::' + symbol)
async def watch_balance(self, params={}) -> Balances:
"""
watch balance and get the amount of funds available for trading or funds locked in orders
https://www.bitmex.com/app/wsAPI#Subscriptions
: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()
messageHash = 'margin'
url = self.urls['api']['ws']
request: dict = {
'op': 'subscribe',
'args': [
messageHash,
],
}
return await self.watch(url, messageHash, self.extend(request, params), messageHash)
def handle_balance(self, client: Client, message):
#
# {
# "table": "margin",
# "action": "partial",
# "keys": ["account"],
# "types": {
# "account": "long",
# "currency": "symbol",
# "riskLimit": "long",
# "prevState": "symbol",
# "state": "symbol",
# "action": "symbol",
# "amount": "long",
# "pendingCredit": "long",
# "pendingDebit": "long",
# "confirmedDebit": "long",
# "prevRealisedPnl": "long",
# "prevUnrealisedPnl": "long",
# "grossComm": "long",
# "grossOpenCost": "long",
# "grossOpenPremium": "long",
# "grossExecCost": "long",
# "grossMarkValue": "long",
# "riskValue": "long",
# "taxableMargin": "long",
# "initMargin": "long",
# "maintMargin": "long",
# "sessionMargin": "long",
# "targetExcessMargin": "long",
# "varMargin": "long",
# "realisedPnl": "long",
# "unrealisedPnl": "long",
# "indicativeTax": "long",
# "unrealisedProfit": "long",
# "syntheticMargin": "long",
# "walletBalance": "long",
# "marginBalance": "long",
# "marginBalancePcnt": "float",
# "marginLeverage": "float",
# "marginUsedPcnt": "float",
# "excessMargin": "long",
# "excessMarginPcnt": "float",
# "availableMargin": "long",
# "withdrawableMargin": "long",
# "timestamp": "timestamp",
# "grossLastValue": "long",
# "commission": "float"
# },
# "foreignKeys": {},
# "attributes": {account: "sorted"},
# "filter": {account: 1455728},
# "data": [
# {
# "account": 1455728,
# "currency": "XBt",
# "riskLimit": 1000000000000,
# "prevState": '',
# "state": '',
# "action": '',
# "amount": 263542,
# "pendingCredit": 0,
# "pendingDebit": 0,
# "confirmedDebit": 0,
# "prevRealisedPnl": 0,
# "prevUnrealisedPnl": 0,
# "grossComm": 0,
# "grossOpenCost": 0,
# "grossOpenPremium": 0,
# "grossExecCost": 0,
# "grossMarkValue": 0,
# "riskValue": 0,
# "taxableMargin": 0,
# "initMargin": 0,
# "maintMargin": 0,
# "sessionMargin": 0,
# "targetExcessMargin": 0,
# "varMargin": 0,
# "realisedPnl": 0,
# "unrealisedPnl": 0,
# "indicativeTax": 0,
# "unrealisedProfit": 0,
# "syntheticMargin": null,
# "walletBalance": 263542,
# "marginBalance": 263542,
# "marginBalancePcnt": 1,
# "marginLeverage": 0,
# "marginUsedPcnt": 0,
# "excessMargin": 263542,
# "excessMarginPcnt": 1,
# "availableMargin": 263542,
# "withdrawableMargin": 263542,
# "timestamp": "2020-08-03T12:01:01.246Z",
# "grossLastValue": 0,
# "commission": null
# }
# ]
# }
#
data = self.safe_value(message, 'data')
balance = self.parse_balance(data)
self.balance = self.extend(self.balance, balance)
messageHash = self.safe_string(message, 'table')
client.resolve(self.balance, messageHash)
def handle_trades(self, client: Client, message):
#
# initial snapshot
#
# {
# "table": "trade",
# "action": "partial",
# "keys": [],
# "types": {
# "timestamp": "timestamp",
# "symbol": "symbol",
# "side": "symbol",
# "size": "long",
# "price": "float",
# "tickDirection": "symbol",
# "trdMatchID": "guid",
# "grossValue": "long",
# "homeNotional": "float",
# "foreignNotional": "float"
# },
# "foreignKeys": {symbol: "instrument", side: "side"},
# "attributes": {timestamp: "sorted", symbol: "grouped"},
# "filter": {symbol: "XBTUSD"},
# "data": [
# {
# "timestamp": "2020-01-30T17:03:07.854Z",
# "symbol": "XBTUSD",
# "side": "Buy",
# "size": 15000,
# "price": 9378,
# "tickDirection": "ZeroPlusTick",
# "trdMatchID": "5b426e7f-83d1-2c80-295d-ee995b8ceb4a",
# "grossValue": 159945000,
# "homeNotional": 1.59945,
# "foreignNotional": 15000
# }
# ]
# }
#
# updates
#
# {
# "table": "trade",
# "action": "insert",
# "data": [
# {
# "timestamp": "2020-01-30T17:31:40.160Z",
# "symbol": "XBTUSD",
# "side": "Sell",
# "size": 37412,
# "price": 9521.5,
# "tickDirection": "ZeroMinusTick",
# "trdMatchID": "a4bfc6bc-6cf1-1a11-622e-270eef8ca5c7",
# "grossValue": 392938236,
# "homeNotional": 3.92938236,
# "foreignNotional": 37412
# }
# ]
# }
#
table = 'trade'
data = self.safe_value(message, 'data', [])
dataByMarketIds = self.group_by(data, 'symbol')
marketIds = list(dataByMarketIds.keys())
for i in range(0, len(marketIds)):
marketId = marketIds[i]
market = self.safe_market(marketId)
symbol = market['symbol']
messageHash = table + ':' + symbol
trades = self.parse_trades(dataByMarketIds[marketId], 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_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a particular symbol
https://www.bitmex.com/app/wsAPI#Subscriptions
: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 authenticate(self, params={}):
url = self.urls['api']['ws']
client = self.client(url)
messageHash = 'authenticated'
future = client.reusableFuture(messageHash)
authenticated = self.safe_value(client.subscriptions, messageHash)
if authenticated is None:
self.check_required_credentials()
timestamp = self.milliseconds()
payload = 'GET' + '/realtime' + str(timestamp)
signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256)
request: dict = {
'op': 'authKeyExpires',
'args': [
self.apiKey,
timestamp,
signature,
],
}
message = self.extend(request, params)
self.watch(url, messageHash, message, messageHash)
return await future
def handle_authentication_message(self, client: Client, message):
authenticated = self.safe_bool(message, 'success', False)
messageHash = 'authenticated'
if authenticated:
# we resolve the future here permanently so authentication only happens once
future = self.safe_value(client.futures, messageHash)
future.resolve(True)
else:
error = AuthenticationError(self.json(message))
client.reject(error, messageHash)
if messageHash in client.subscriptions:
del client.subscriptions[messageHash]
async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
"""
watch all open positions
https://www.bitmex.com/app/wsAPI#Subscriptions
:param str[]|None symbols: list of unified market symbols
:param int [since]: the earliest time in ms to watch positions for
: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()
await self.authenticate()
subscriptionHash = 'position'
messageHash = 'positions'
if not self.is_empty(symbols):
messageHash = '::' + ','.join(symbols)
url = self.urls['api']['ws']
request: dict = {
'op': 'subscribe',
'args': [
subscriptionHash,
],
}
newPositions = await self.watch(url, messageHash, request, subscriptionHash)
if self.newUpdates:
return newPositions
return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True)
def handle_positions(self, client, message):
#
# partial
# {
# table: 'position',
# action: 'partial',
# keys: ['account', 'symbol'],
# types: {
# account: 'long',
# symbol: 'symbol',
# currency: 'symbol',
# underlying: 'symbol',
# quoteCurrency: 'symbol',
# commission: 'float',
# initMarginReq: 'float',
# maintMarginReq: 'float',
# riskLimit: 'long',
# leverage: 'float',
# crossMargin: 'boolean',
# deleveragePercentile: 'float',
# rebalancedPnl: 'long',
# prevRealisedPnl: 'long',
# prevUnrealisedPnl: 'long',
# openingQty: 'long',
# openOrderBuyQty: 'long',
# openOrderBuyCost: 'long',
# openOrderBuyPremium: 'long',
# openOrderSellQty: 'long',
# openOrderSellCost: 'long',
# openOrderSellPremium: 'long',
# currentQty: 'long',
# currentCost: 'long',
# currentComm: 'long',
# realisedCost: 'long',
# unrealisedCost: 'long',
# grossOpenPremium: 'long',
# isOpen: 'boolean',
# markPrice: 'float',
# markValue: 'long',
# riskValue: 'long',
# homeNotional: 'float',
# foreignNotional: 'float',
# posState: 'symbol',
# posCost: 'long',
# posCross: 'long',
# posComm: 'long',
# posLoss: 'long',
# posMargin: 'long',
# posMaint: 'long',
# initMargin: 'long',
# maintMargin: 'long',
# realisedPnl: 'long',
# unrealisedPnl: 'long',
# unrealisedPnlPcnt: 'float',
# unrealisedRoePcnt: 'float',
# avgCostPrice: 'float',
# avgEntryPrice: 'float',
# breakEvenPrice: 'float',
# marginCallPrice: 'float',
# liquidationPrice: 'float',
# bankruptPrice: 'float',
# timestamp: 'timestamp'
# },
# filter: {account: 412475},
# data: [
# {
# account: 412475,
# symbol: 'XBTUSD',
# currency: 'XBt',
# underlying: 'XBT',
# quoteCurrency: 'USD',
# commission: 0.00075,
# initMarginReq: 0.01,
# maintMarginReq: 0.0035,
# riskLimit: 20000000000,
# leverage: 100,
# crossMargin: True,
# deleveragePercentile: 1,
# rebalancedPnl: 0,
# prevRealisedPnl: 0,
# prevUnrealisedPnl: 0,
# openingQty: 400,
# openOrderBuyQty: 0,
# openOrderBuyCost: 0,
# openOrderBuyPremium: 0,
# openOrderSellQty: 0,
# openOrderSellCost: 0,
# openOrderSellPremium: 0,
# currentQty: 400,
# currentCost: -912269,
# currentComm: 684,
# realisedCost: 0,
# unrealisedCost: -912269,
# grossOpenPremium: 0,
# isOpen: True,
# markPrice: 43772,
# markValue: -913828,
# riskValue: 913828,
# homeNotional: 0.00913828,
# foreignNotional: -400,
# posCost: -912269,
# posCross: 1559,
# posComm: 694,
# posLoss: 0,
# posMargin: 11376,
# posMaint: 3887,
# initMargin: 0,
# maintMargin: 9817,
# realisedPnl: -684,
# unrealisedPnl: -1559,
# unrealisedPnlPcnt: -0.0017,
# unrealisedRoePcnt: -0.1709,
# avgCostPrice: 43846.7643,
# avgEntryPrice: 43846.7643,
# breakEvenPrice: 43880,
# marginCallPrice: 20976,
# liquidationPrice: 20976,
# bankruptPrice: 20941,
# timestamp: '2023-12-07T00:09:00.709Z'
# }
# ]
# }
# update
# {
# table: 'position',
# action: 'update',
# data: [
# {
# account: 412475,
# symbol: 'XBTUSD',
# currency: 'XBt',
# currentQty: 400,
# markPrice: 43772.75,
# markValue: -913812,
# riskValue: 913812,
# homeNotional: 0.00913812,
# posCross: 1543,
# posComm: 693,
# posMargin: 11359,
# posMaint: 3886,
# maintMargin: 9816,
# unrealisedPnl: -1543,
# unrealisedRoePcnt: -0.1691,
# liquidationPrice: 20976,
# timestamp: '2023-12-07T00:09:10.760Z'
# }
# ]
# }
#
if self.positions is None:
self.positions = ArrayCacheBySymbolBySide()
cache = self.positions
rawPositions = self.safe_value(message, 'data', [])
newPositions = []
for i in range(0, len(rawPositions)):
rawPosition = rawPositions[i]
position = self.parse_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')
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://www.bitmex.com/app/wsAPI#Subscriptions
: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()
await self.authenticate()
name = 'order'
subscriptionHash = name
messageHash = name
if symbol is not None:
symbol = self.symbol(symbol)
messageHash += ':' + symbol
url = self.urls['api']['ws']
request: dict = {
'op': 'subscribe',
'args': [
subscriptionHash,
],
}
orders = await self.watch(url, messageHash, request, subscriptionHash)
if self.newUpdates:
limit = orders.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
def handle_orders(self, client: Client, message):
#
# {
# "table": "order",
# "action": "partial",
# "keys": ["orderID"],
# "types": {
# "orderID": "guid",
# "clOrdID": "string",
# "clOrdLinkID": "symbol",
# "account": "long",
# "symbol": "symbol",
# "side": "symbol",
# "simpleOrderQty": "float",
# "orderQty": "long",
# "price": "float",
# "displayQty": "long",
# "stopPx": "float",
# "pegOffsetValue": "float",
# "pegPriceType": "symbol",
# "currency": "symbol",
# "settlCurrency": "symbol",
# "ordType": "symbol",
# "timeInForce": "symbol",
# "execInst": "symbol",
# "contingencyType": "symbol",
# "exDestination": "symbol",
# "ordStatus": "symbol",
# "triggered": "symbol",
# "workingIndicator": "boolean",
# "ordRejReason": "symbol",
# "simpleLeavesQty": "float",
# "leavesQty": "long",
# "simpleCumQty": "float",
# "cumQty": "long",
# "avgPx": "float",
# "multiLegReportingType": "symbol",
# "text": "string",
# "transactTime": "timestamp",
# "timestamp": "timestamp"
# },
# "foreignKeys": {symbol: 'instrument', side: "side", ordStatus: "ordStatus"},
# "attributes": {
# "orderID": "grouped",
# "account": "grouped",
# "ordStatus": "grouped",
# "workingIndicator": "grouped"
# },
# "filter": {account: 1455728},
# "data": [
# {
# "orderID": "56222c7a-9956-413a-82cf-99f4812c214b",
# "clOrdID": '',
# "clOrdLinkID": '',
# "account": 1455728,
# "symbol": "XBTUSD",
# "side": "Sell",
# "simpleOrderQty": null,
# "orderQty": 1,
# "price": 40000,
# "displayQty": null,
# "stopPx": null,
# "pegOffsetValue": null,
# "pegPriceType": '',
# "currency": "USD",
# "settlCurrency": "XBt",
# "ordType": "Limit",
# "timeInForce": "GoodTillCancel",
# "execInst": '',
# "contingencyType": '',
# "exDestination": "XBME",
# "ordStatus": "New",
# "triggered": '',
# "workingIndicator": True,
# "ordRejReason": '',
# "simpleLeavesQty": null,
# "leavesQty": 1,
# "simpleCumQty": null,
# "cumQty": 0,
# "avgPx": null,
# "multiLegReportingType": "SingleSecurity",
# "text": "Submitted via API.",
# "transactTime": "2021-01-02T21:38:49.246Z",
# "timestamp": "2021-01-02T21:38:49.246Z"
# }
# ]
# }
#
# {
# "table": "order",
# "action": "insert",
# "data": [
# {
# "orderID": "fa993d8e-f7e4-46ed-8097-04f8e9393585",
# "clOrdID": '',
# "clOrdLinkID": '',
# "account": 1455728,
# "symbol": "XBTUSD",
# "side": "Sell",
# "simpleOrderQty": null,
# "orderQty": 1,
# "price": 40000,
# "displayQty": null,
# "stopPx": null,
# "pegOffsetValue": null,
# "pegPriceType": '',
# "currency": "USD",
# "settlCurrency": "XBt",
# "ordType": "Limit",
# "timeInForce": "GoodTillCancel",
# "execInst": '',
# "contingencyType": '',
# "exDestination": "XBME",
# "ordStatus": "New",
# "triggered": '',
# "workingIndicator": True,
# "ordRejReason": '',
# "simpleLeavesQty": null,
# "leavesQty": 1,
# "simpleCumQty": null,
# "cumQty": 0,
# "avgPx": null,
# "multiLegReportingType": "SingleSecurity",
# "text": "Submitted via API.",
# "transactTime": "2021-01-02T23:49:02.286Z",
# "timestamp": "2021-01-02T23:49:02.286Z"
# }
# ]
# }
#
#
#
# {
# "table": "order",
# "action": "update",
# "data": [
# {
# "orderID": "fa993d8e-f7e4-46ed-8097-04f8e9393585",
# "ordStatus": "Canceled",
# "workingIndicator": False,
# "leavesQty": 0,
# "text": "Canceled: Canceled via API.\nSubmitted via API.",
# "timestamp": "2021-01-02T23:50:51.272Z",
# "clOrdID": '',
# "account": 1455728,
# "symbol": "XBTUSD"
# }
# ]
# }
#
data = self.safe_value(message, 'data', [])
messageHash = 'order'
# initial subscription response with multiple orders
dataLength = len(data)
if dataLength > 0:
if self.orders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
stored = self.orders
symbols: dict = {}
for i in range(0, dataLength):
currentOrder = data[i]
orderId = self.safe_string(currentOrder, 'orderID')
previousOrder = self.safe_value(stored.hashmap, orderId)
rawOrder = currentOrder
if previousOrder is not None:
rawOrder = self.extend(previousOrder['info'], currentOrder)
order = self.parse_order(rawOrder)
stored.append(order)
symbol = order['symbol']
symbols[symbol] = True
client.resolve(self.orders, messageHash)
keys = list(symbols.keys())
for i in range(0, len(keys)):
symbol = keys[i]
client.resolve(self.orders, messageHash + ':' + symbol)
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://www.bitmex.com/app/wsAPI#Subscriptions
:param str symbol: unified market symbol of the market trades were made in
:param int [since]: the earliest time in ms to fetch trades for
:param int [limit]: the maximum number of trade structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
"""
await self.load_markets()
await self.authenticate()
name = 'execution'
subscriptionHash = name
messageHash = name
if symbol is not None:
symbol = self.symbol(symbol)
messageHash += ':' + symbol
url = self.urls['api']['ws']
request: dict = {
'op': 'subscribe',
'args': [
subscriptionHash,
],
}
trades = await self.watch(url, messageHash, request, subscriptionHash)
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
def handle_my_trades(self, client: Client, message):
#
# {
# "table":"execution",
# "action":"insert",
# "data":[
# {
# "execID":"0193e879-cb6f-2891-d099-2c4eb40fee21",
# "orderID":"00000000-0000-0000-0000-000000000000",
# "clOrdID":"",
# "clOrdLinkID":"",
# "account":2,
# "symbol":"XBTUSD",
# "side":"Sell",
# "lastQty":1,
# "lastPx":1134.37,
# "underlyingLastPx":null,
# "lastMkt":"XBME",
# "lastLiquidityInd":"RemovedLiquidity",
# "simpleOrderQty":null,
# "orderQty":1,
# "price":1134.37,
# "displayQty":null,
# "stopPx":null,
# "pegOffsetValue":null,
# "pegPriceType":"",
# "currency":"USD",
# "settlCurrency":"XBt",
# "execType":"Trade",
# "ordType":"Limit",
# "timeInForce":"ImmediateOrCancel",
# "execInst":"",
# "contingencyType":"",
# "exDestination":"XBME",
# "ordStatus":"Filled",
# "triggered":"",
# "workingIndicator":false,
# "ordRejReason":"",
# "simpleLeavesQty":0,
# "leavesQty":0,
# "simpleCumQty":0.001,
# "cumQty":1,
# "avgPx":1134.37,
# "commission":0.00075,
# "tradePublishIndicator":"DoNotPublishTrade",
# "multiLegReportingType":"SingleSecurity",
# "text":"Liquidation",
# "trdMatchID":"7f4ab7f6-0006-3234-76f4-ae1385aad00f",
# "execCost":88155,
# "execComm":66,
# "homeNotional":-0.00088155,
# "foreignNotional":1,
# "transactTime":"2017-04-04T22:07:46.035Z",
# "timestamp":"2017-04-04T22:07:46.035Z"
# }
# ]
# }
#
messageHash = self.safe_string(message, 'table')
data = self.safe_value(message, 'data', [])
dataByExecType = self.group_by(data, 'execType')
rawTrades = self.safe_value(dataByExecType, 'Trade', [])
trades = self.parse_trades(rawTrades)
if self.myTrades is None:
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
self.myTrades = ArrayCacheBySymbolById(limit)
stored = self.myTrades
symbols: dict = {}
for j in range(0, len(trades)):
trade = trades[j]
symbol = trade['symbol']
stored.append(trade)
symbols[symbol] = trade
numTrades = len(trades)
if numTrades > 0:
client.resolve(stored, messageHash)
keys = list(symbols.keys())
for i in range(0, len(keys)):
client.resolve(stored, messageHash + ':' + keys[i])
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://www.bitmex.com/app/wsAPI#OrderBookL2
: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
"""
return await self.watch_order_book_for_symbols([symbol], limit, params)
async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://www.bitmex.com/app/wsAPI#OrderBookL2
:param str[] symbols: unified array of symbols
:param int [limit]: the maximum amount of order book entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
table = None
if limit is None:
table = self.safe_string(self.options, 'watchOrderBookLevel', 'orderBookL2')
elif limit == 25:
table = 'orderBookL2_25'
elif limit == 10:
table = 'orderBookL10'
else:
raise ExchangeError(self.id + ' watchOrderBookForSymbols limit argument must be None(L2), 25(L2) or 10(L3)')
await self.load_markets()
symbols = self.market_symbols(symbols)
topics = []
messageHashes = []
for i in range(0, len(symbols)):
symbol = symbols[i]
market = self.market(symbol)
topic = table + ':' + market['id']
topics.append(topic)
messageHash = table + ':' + symbol
messageHashes.append(messageHash)
url = self.urls['api']['ws']
request: dict = {
'op': 'subscribe',
'args': topics,
}
orderbook = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), topics)
return orderbook.limit()
async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a list of symbols
https://www.bitmex.com/app/wsAPI#Subscriptions
: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()
symbols = self.market_symbols(symbols, None, False)
table = 'trade'
topics = []
messageHashes = []
for i in range(0, len(symbols)):
symbol = symbols[i]
market = self.market(symbol)
topic = table + ':' + market['id']
topics.append(topic)
messageHash = table + ':' + symbol
messageHashes.append(messageHash)
url = self.urls['api']['ws']
request: dict = {
'op': 'subscribe',
'args': topics,
}
trades = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), topics)
if self.newUpdates:
first = self.safe_value(trades, 0)
tradeSymbol = self.safe_string(first, 'symbol')
limit = trades.getLimit(tradeSymbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
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://www.bitmex.com/app/wsAPI#Subscriptions
:param str symbol: unified symbol of the market to fetch OHLCV data for
:param str timeframe: the length of time each candle represents
:param int [since]: timestamp in ms of the earliest candle to fetch
:param int [limit]: the maximum amount of candles to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns int[][]: A list of candles ordered, open, high, low, close, volume
"""
await self.load_markets()
market = self.market(symbol)
symbol = market['symbol']
table = 'tradeBin' + self.safe_string(self.timeframes, timeframe, timeframe)
messageHash = table + ':' + market['id']
url = self.urls['api']['ws']
request: dict = {
'op': 'subscribe',
'args': [
messageHash,
],
}
ohlcv = await self.watch(url, messageHash, self.extend(request, params), messageHash)
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):
#
# {
# "table": "tradeBin1m",
# "action": "partial",
# "keys": [],
# "types": {
# "timestamp": "timestamp",
# "symbol": "symbol",
# "open": "float",
# "high": "float",
# "low": "float",
# "close": "float",
# "trades": "long",
# "volume": "long",
# "vwap": "float",
# "lastSize": "long",
# "turnover": "long",
# "homeNotional": "float",
# "foreignNotional": "float"
# },
# "foreignKeys": {symbol: "instrument"},
# "attributes": {timestamp: "sorted", symbol: "grouped"},
# "filter": {symbol: "XBTUSD"},
# "data": [
# {
# "timestamp": "2020-02-03T01:13:00.000Z",
# "symbol": "XBTUSD",
# "open": 9395,
# "high": 9395.5,
# "low": 9394.5,
# "close": 9395,
# "trades": 221,
# "volume": 839204,
# "vwap": 9394.9643,
# "lastSize": 1874,
# "turnover": 8932641535,
# "homeNotional": 89.32641534999999,
# "foreignNotional": 839204
# }
# ]
# }
#
#
# {
# "table": "tradeBin1m",
# "action": "insert",
# "data": [
# {
# "timestamp": "2020-02-03T18:28:00.000Z",
# "symbol": "XBTUSD",
# "open": 9256,
# "high": 9256.5,
# "low": 9256,
# "close": 9256,
# "trades": 29,
# "volume": 79057,
# "vwap": 9256.688,
# "lastSize": 100,
# "turnover": 854077082,
# "homeNotional": 8.540770820000002,
# "foreignNotional": 79057
# }
# ]
# }
#
table = self.safe_string(message, 'table')
interval = table.replace('tradeBin', '')
timeframe = self.find_timeframe(interval)
duration = self.parse_timeframe(timeframe)
candles = self.safe_value(message, 'data', [])
results: dict = {}
for i in range(0, len(candles)):
candle = candles[i]
marketId = self.safe_string(candle, 'symbol')
market = self.safe_market(marketId)
symbol = market['symbol']
messageHash = table + ':' + market['id']
result = [
self.parse8601(self.safe_string(candle, 'timestamp')) - duration * 1000,
None, # set open price to None, see: https://github.com/ccxt/ccxt/pull/21356#issuecomment-1969565862
self.safe_float(candle, 'high'),
self.safe_float(candle, 'low'),
self.safe_float(candle, 'close'),
self.safe_float(candle, 'volume'),
]
self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
stored = self.safe_value(self.ohlcvs[symbol], timeframe)
if stored is None:
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
stored = ArrayCacheByTimestamp(limit)
self.ohlcvs[symbol][timeframe] = stored
stored.append(result)
results[messageHash] = stored
messageHashes = list(results.keys())
for i in range(0, len(messageHashes)):
messageHash = messageHashes[i]
client.resolve(results[messageHash], messageHash)
async def watch_heartbeat(self, params={}):
await self.load_markets()
event = 'heartbeat'
url = self.urls['api']['ws']
return await self.watch(url, event)
def handle_order_book(self, client: Client, message):
#
# first snapshot
#
# {
# "table": "orderBookL2",
# "action": "partial",
# "keys": ['symbol', "id", "side"],
# "types": {
# "symbol": "symbol",
# "id": "long",
# "side": "symbol",
# "size": "long",
# "price": "float"
# },
# "foreignKeys": {symbol: "instrument", side: "side"},
# "attributes": {symbol: "parted", id: "sorted"},
# "filter": {symbol: "XBTUSD"},
# "data": [
# {symbol: "XBTUSD", id: 8700000100, side: "Sell", size: 1, price: 999999},
# {symbol: "XBTUSD", id: 8700000200, side: "Sell", size: 3, price: 999998},
# {symbol: "XBTUSD", id: 8716991250, side: "Sell", size: 26, price: 830087.5},
# {symbol: "XBTUSD", id: 8728701950, side: "Sell", size: 1720, price: 712980.5},
# ]
# }
#
# subsequent updates
#
# {
# "table": "orderBookL2",
# "action": "update",
# "data": [
# {
# "table": "orderBookL2",
# "action": "insert",
# "data": [
# {
# "symbol": "ETH_USDT",
# "id": 85499965912,
# "side": "Buy",
# "size": 83000000,
# "price": 1704.4,
# "timestamp": "2023-03-26T22:29:00.299Z"
# }
# ]
# }
# ...
# ]
# }
#
action = self.safe_string(message, 'action')
table = self.safe_string(message, 'table')
if table is None:
return # protecting from weird updates
data = self.safe_value(message, 'data', [])
# if it's an initial snapshot
if action == 'partial':
filter = self.safe_dict(message, 'filter', {})
marketId = self.safe_value(filter, 'symbol')
if marketId is None:
return # protecting from weird update
market = self.safe_market(marketId)
symbol = market['symbol']
if table == 'orderBookL2':
self.orderbooks[symbol] = self.indexed_order_book()
elif table == 'orderBookL2_25':
self.orderbooks[symbol] = self.indexed_order_book({}, 25)
elif table == 'orderBook10':
self.orderbooks[symbol] = self.indexed_order_book({}, 10)
orderbook = self.orderbooks[symbol]
orderbook['symbol'] = symbol
for i in range(0, len(data)):
price = self.safe_float(data[i], 'price')
size = self.convertFromRawQuantity(symbol, self.safe_string(data[i], 'size'))
id = self.safe_string(data[i], 'id')
side = self.safe_string(data[i], 'side')
side = 'bids' if (side == 'Buy') else 'asks'
bookside = orderbook[side]
bookside.storeArray([price, size, id])
datetime = self.safe_string(data[i], 'timestamp')
orderbook['timestamp'] = self.parse8601(datetime)
orderbook['datetime'] = datetime
messageHash = table + ':' + symbol
client.resolve(orderbook, messageHash)
else:
numUpdatesByMarketId: dict = {}
for i in range(0, len(data)):
marketId = self.safe_value(data[i], 'symbol')
if marketId is None:
return # protecting from weird update
if not (marketId in numUpdatesByMarketId):
numUpdatesByMarketId[marketId] = 0
numUpdatesByMarketId[marketId] = self.sum(numUpdatesByMarketId, 1)
market = self.safe_market(marketId)
symbol = market['symbol']
orderbook = self.orderbooks[symbol]
price = self.safe_number(data[i], 'price')
size = 0 if (action == 'delete') else self.convertFromRawQuantity(symbol, self.safe_string(data[i], 'size', '0'))
id = self.safe_string(data[i], 'id')
side = self.safe_string(data[i], 'side')
side = 'bids' if (side == 'Buy') else 'asks'
bookside = orderbook[side]
bookside.storeArray([price, size, id])
datetime = self.safe_string(data[i], 'timestamp')
orderbook['timestamp'] = self.parse8601(datetime)
orderbook['datetime'] = datetime
marketIds = list(numUpdatesByMarketId.keys())
for i in range(0, len(marketIds)):
marketId = marketIds[i]
market = self.safe_market(marketId)
symbol = market['symbol']
messageHash = table + ':' + symbol
orderbook = self.orderbooks[symbol]
client.resolve(orderbook, messageHash)
def handle_system_status(self, client: Client, message):
#
# todo answer the question whether handleSystemStatus should be renamed
# and unified for any usage pattern that
# involves system status and maintenance updates
#
# {
# "info": "Welcome to the BitMEX Realtime API.",
# "version": "2019-11-22T00:24:37.000Z",
# "timestamp": "2019-11-23T09:02:27.771Z",
# "docs": "https://www.bitmex.com/app/wsAPI",
# "limit": {remaining: 39}
# }
#
return message
def handle_subscription_status(self, client: Client, message):
#
# {
# "success": True,
# "subscribe": "orderBookL2:XBTUSD",
# "request": {op: "subscribe", args: ["orderBookL2:XBTUSD"]}
# }
#
return message
def handle_error_message(self, client: Client, message) -> Bool:
#
# generic error format
#
# {"error": errorMessage}
#
# examples
#
# {
# "status": 429,
# "error": "Rate limit exceeded, retry in 1 seconds.",
# "meta": {"retryAfter": 1},
# "request": {"op": "subscribe", "args": "orderBook"},
# }
#
# {"error": "Rate limit exceeded, retry in 29 seconds."}
#
error = self.safe_string(message, 'error')
if error is not None:
request = self.safe_value(message, 'request', {})
args = self.safe_value(request, 'args', [])
numArgs = len(args)
if numArgs > 0:
messageHash = args[0]
broad = self.exceptions['ws']['broad']
broadKey = self.find_broadly_matched_key(broad, error)
exception = None
if broadKey is None:
exception = ExchangeError(error) # c# requirement for now
else:
exception = broad[broadKey](error)
client.reject(exception, messageHash)
return False
return True
def handle_message(self, client: Client, message):
#
# {
# "info": "Welcome to the BitMEX Realtime API.",
# "version": "2019-11-22T00:24:37.000Z",
# "timestamp": "2019-11-23T09:04:42.569Z",
# "docs": "https://www.bitmex.com/app/wsAPI",
# "limit": {remaining: 38}
# }
#
# {
# "success": True,
# "subscribe": "orderBookL2:XBTUSD",
# "request": {op: "subscribe", args: ["orderBookL2:XBTUSD"]}
# }
#
# {
# "table": "orderBookL2",
# "action": "update",
# "data": [
# {symbol: "XBTUSD", id: 8799284800, side: "Sell", size: 721000},
# {symbol: "XBTUSD", id: 8799285100, side: "Sell", size: 70590},
# {symbol: "XBTUSD", id: 8799285550, side: "Sell", size: 217652},
# {symbol: "XBTUSD", id: 8799285850, side: "Sell", size: 105578},
# {symbol: "XBTUSD", id: 8799286350, side: "Sell", size: 172093},
# {symbol: "XBTUSD", id: 8799286650, side: "Sell", size: 201125},
# {symbol: "XBTUSD", id: 8799288950, side: "Buy", size: 47552},
# {symbol: "XBTUSD", id: 8799289250, side: "Buy", size: 78217},
# {symbol: "XBTUSD", id: 8799289700, side: "Buy", size: 193677},
# {symbol: "XBTUSD", id: 8799290000, side: "Buy", size: 818161},
# {symbol: "XBTUSD", id: 8799290500, side: "Buy", size: 218806},
# {symbol: "XBTUSD", id: 8799290800, side: "Buy", size: 102946}
# ]
# }
#
if self.handle_error_message(client, message):
table = self.safe_string(message, 'table')
methods: dict = {
'orderBookL2': self.handle_order_book,
'orderBookL2_25': self.handle_order_book,
'orderBook10': self.handle_order_book,
'instrument': self.handle_ticker,
'trade': self.handle_trades,
'tradeBin1m': self.handle_ohlcv,
'tradeBin5m': self.handle_ohlcv,
'tradeBin1h': self.handle_ohlcv,
'tradeBin1d': self.handle_ohlcv,
'order': self.handle_orders,
'execution': self.handle_my_trades,
'margin': self.handle_balance,
'liquidation': self.handle_liquidation,
'position': self.handle_positions,
}
method = self.safe_value(methods, table)
if method is None:
request = self.safe_value(message, 'request', {})
op = self.safe_value(request, 'op')
if op == 'authKeyExpires':
self.handle_authentication_message(client, message)
else:
method(client, message)