1241 lines
52 KiB
Python
1241 lines
52 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
|
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
|
|
|
import ccxt.async_support
|
|
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
|
|
from ccxt.base.types import Any, 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 ArgumentsRequired
|
|
|
|
|
|
class backpack(ccxt.async_support.backpack):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(backpack, self).describe(), {
|
|
'has': {
|
|
'ws': True,
|
|
'watchBalance': False,
|
|
'watchBidsAsks': True,
|
|
'watchMyTrades': False,
|
|
'watchOHLCV': True,
|
|
'watchOHLCVForSymbols': True,
|
|
'watchOrderBook': True,
|
|
'watchOrderBookForSymbols': True,
|
|
'watchOrders': True,
|
|
'watchPositions': True,
|
|
'watchTicker': True,
|
|
'watchTickers': True,
|
|
'watchTrades': True,
|
|
'watchTradesForSymbols': True,
|
|
'unwatchBidsAsks': True,
|
|
'unwatchOHLCV': True,
|
|
'unwatchOHLCVForSymbols': True,
|
|
'unwatchOrderBook': True,
|
|
'unwatchOrderBookForSymbols': True,
|
|
'unwatchTicker': True,
|
|
'unwatchTickers': True,
|
|
'unWatchTrades': True,
|
|
'unWatchTradesForSymbols': True,
|
|
'unWatchOrders': True,
|
|
'unWatchPositions': True,
|
|
},
|
|
'urls': {
|
|
'api': {
|
|
'ws': {
|
|
'public': 'wss://ws.backpack.exchange',
|
|
'private': 'wss://ws.backpack.exchange',
|
|
},
|
|
},
|
|
},
|
|
'options': {
|
|
'timeframes': {
|
|
},
|
|
},
|
|
'streaming': {
|
|
'ping': self.ping,
|
|
'keepAlive': 119000,
|
|
},
|
|
})
|
|
|
|
async def watch_public(self, topics, messageHashes, params={}, unwatch=False):
|
|
await self.load_markets()
|
|
url = self.urls['api']['ws']['public']
|
|
method = 'UNSUBSCRIBE' if unwatch else 'SUBSCRIBE'
|
|
request: dict = {
|
|
'method': method,
|
|
'params': topics,
|
|
}
|
|
message = self.deep_extend(request, params)
|
|
if unwatch:
|
|
self.handle_unsubscriptions(url, messageHashes, message)
|
|
return None
|
|
return await self.watch_multiple(url, messageHashes, message, messageHashes)
|
|
|
|
async def watch_private(self, topics, messageHashes, params={}, unwatch=False):
|
|
self.check_required_credentials()
|
|
url = self.urls['api']['ws']['private']
|
|
instruction = 'subscribe'
|
|
ts = str(self.nonce())
|
|
method = 'UNSUBSCRIBE' if unwatch else 'SUBSCRIBE'
|
|
recvWindow = self.safe_string_2(self.options, 'recvWindow', 'X-Window', '5000')
|
|
payload = 'instruction=' + instruction + '&' + 'timestamp=' + ts + '&window=' + recvWindow
|
|
secretBytes = self.base64_to_binary(self.secret)
|
|
seed = self.array_slice(secretBytes, 0, 32)
|
|
signature = self.eddsa(self.encode(payload), seed, 'ed25519')
|
|
request: dict = {
|
|
'method': method,
|
|
'params': topics,
|
|
'signature': [self.apiKey, signature, ts, recvWindow],
|
|
}
|
|
message = self.deep_extend(request, params)
|
|
if unwatch:
|
|
self.handle_unsubscriptions(url, messageHashes, message)
|
|
return None
|
|
return await self.watch_multiple(url, messageHashes, message, messageHashes)
|
|
|
|
def handle_unsubscriptions(self, url: str, messageHashes: List[str], message: dict):
|
|
client = self.client(url)
|
|
self.watch_multiple(url, messageHashes, message, messageHashes)
|
|
for i in range(0, len(messageHashes)):
|
|
messageHash = messageHashes[i]
|
|
subMessageHash = messageHash.replace('unsubscribe:', '')
|
|
self.clean_unsubscription(client, subMessageHash, messageHash)
|
|
if messageHash.find('ticker') >= 0:
|
|
symbol = messageHash.replace('unsubscribe:ticker:', '')
|
|
if symbol in self.tickers:
|
|
del self.tickers[symbol]
|
|
elif messageHash.find('bidask') >= 0:
|
|
symbol = messageHash.replace('unsubscribe:bidask:', '')
|
|
if symbol in self.bidsasks:
|
|
del self.bidsasks[symbol]
|
|
elif messageHash.find('candles') >= 0:
|
|
splitHashes = messageHash.split(':')
|
|
symbol = self.safe_string(splitHashes, 2)
|
|
timeframe = self.safe_string(splitHashes, 3)
|
|
if symbol in self.ohlcvs:
|
|
if timeframe in self.ohlcvs[symbol]:
|
|
del self.ohlcvs[symbol][timeframe]
|
|
elif messageHash.find('orderbook') >= 0:
|
|
symbol = messageHash.replace('unsubscribe:orderbook:', '')
|
|
if symbol in self.orderbooks:
|
|
del self.orderbooks[symbol]
|
|
elif messageHash.find('trades') >= 0:
|
|
symbol = messageHash.replace('unsubscribe:trades:', '')
|
|
if symbol in self.trades:
|
|
del self.trades[symbol]
|
|
elif messageHash.find('orders') >= 0:
|
|
if messageHash == 'unsubscribe:orders':
|
|
cache = self.orders
|
|
keys = list(cache.keys())
|
|
for j in range(0, len(keys)):
|
|
symbol = keys[j]
|
|
del self.orders[symbol]
|
|
else:
|
|
symbol = messageHash.replace('unsubscribe:orders:', '')
|
|
if symbol in self.orders:
|
|
del self.orders[symbol]
|
|
elif messageHash.find('positions') >= 0:
|
|
if messageHash == 'unsubscribe:positions':
|
|
cache = self.positions
|
|
keys = list(cache.keys())
|
|
for j in range(0, len(keys)):
|
|
symbol = keys[j]
|
|
del self.positions[symbol]
|
|
else:
|
|
symbol = messageHash.replace('unsubscribe:positions:', '')
|
|
if symbol in self.positions:
|
|
del self.positions[symbol]
|
|
|
|
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://docs.backpack.exchange/#tag/Streams/Public/Ticker
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
topic = 'ticker' + '.' + market['id']
|
|
messageHash = 'ticker' + ':' + symbol
|
|
return await self.watch_public([topic], [messageHash], params)
|
|
|
|
async def un_watch_ticker(self, symbol: str, params={}) -> Any:
|
|
"""
|
|
unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/Ticker
|
|
|
|
: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>`
|
|
"""
|
|
return await self.un_watch_tickers([symbol], params)
|
|
|
|
async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/Ticker
|
|
|
|
: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)
|
|
messageHashes = []
|
|
topics = []
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
marketId = self.market_id(symbol)
|
|
messageHashes.append('ticker:' + symbol)
|
|
topics.append('ticker.' + marketId)
|
|
await self.watch_public(topics, messageHashes, params)
|
|
return self.filter_by_array(self.tickers, 'symbol', symbols)
|
|
|
|
async def un_watch_tickers(self, symbols: Strings = None, params={}) -> Any:
|
|
"""
|
|
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/Ticker
|
|
|
|
: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)
|
|
topics = []
|
|
messageHashes = []
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
marketId = self.market_id(symbol)
|
|
topics.append('ticker.' + marketId)
|
|
messageHashes.append('unsubscribe:ticker:' + symbol)
|
|
return await self.watch_public(topics, messageHashes, params, True)
|
|
|
|
def handle_ticker(self, client: Client, message):
|
|
#
|
|
# {
|
|
# data: {
|
|
# E: '1754176123312507',
|
|
# V: '19419526.742584',
|
|
# c: '3398.57',
|
|
# e: 'ticker',
|
|
# h: '3536.65',
|
|
# l: '3371.8',
|
|
# n: 17152,
|
|
# o: '3475.45',
|
|
# s: 'ETH_USDC',
|
|
# v: '5573.5827'
|
|
# },
|
|
# stream: 'bookTicker.ETH_USDC'
|
|
# }
|
|
#
|
|
ticker = self.safe_dict(message, 'data', {})
|
|
marketId = self.safe_string(ticker, 's')
|
|
market = self.safe_market(marketId)
|
|
symbol = self.safe_symbol(marketId, market)
|
|
parsedTicker = self.parse_ws_ticker(ticker, market)
|
|
messageHash = 'ticker' + ':' + symbol
|
|
self.tickers[symbol] = parsedTicker
|
|
client.resolve(parsedTicker, messageHash)
|
|
|
|
def parse_ws_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# {
|
|
# E: '1754178406415232',
|
|
# V: '19303818.6923',
|
|
# c: '3407.54',
|
|
# e: 'ticker',
|
|
# h: '3536.65',
|
|
# l: '3369.18',
|
|
# n: 17272,
|
|
# o: '3481.71',
|
|
# s: 'ETH_USDC',
|
|
# v: '5542.3911'
|
|
# }
|
|
#
|
|
microseconds = self.safe_integer(ticker, 'E')
|
|
timestamp = self.parse_to_int(microseconds / 1000)
|
|
marketId = self.safe_string(ticker, 's')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = self.safe_symbol(marketId, market)
|
|
last = self.safe_string(ticker, 'c')
|
|
open = self.safe_string(ticker, 'o')
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': self.safe_number(ticker, 'h'),
|
|
'low': self.safe_number(ticker, 'l'),
|
|
'bid': None,
|
|
'bidVolume': None,
|
|
'ask': None,
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': open,
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': None,
|
|
'average': None,
|
|
'baseVolume': self.safe_string(ticker, 'v'),
|
|
'quoteVolume': self.safe_string(ticker, 'V'),
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
watches best bid & ask for symbols
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/Book-ticker
|
|
|
|
: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)
|
|
topics = []
|
|
messageHashes = []
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
marketId = self.market_id(symbol)
|
|
topics.append('bookTicker.' + marketId)
|
|
messageHashes.append('bidask:' + symbol)
|
|
await self.watch_public(topics, messageHashes, params)
|
|
return self.filter_by_array(self.bidsasks, 'symbol', symbols)
|
|
|
|
async def un_watch_bids_asks(self, symbols: Strings = None, params={}) -> Any:
|
|
"""
|
|
unWatches 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)
|
|
topics = []
|
|
messageHashes = []
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
marketId = self.market_id(symbol)
|
|
topics.append('bookTicker.' + marketId)
|
|
messageHashes.append('unsubscribe:bidask:' + symbol)
|
|
return await self.watch_public(topics, messageHashes, params, True)
|
|
|
|
def handle_bid_ask(self, client: Client, message):
|
|
#
|
|
# {
|
|
# data: {
|
|
# A: '0.4087',
|
|
# B: '0.0020',
|
|
# E: '1754517402450016',
|
|
# T: '1754517402449064',
|
|
# a: '3667.50',
|
|
# b: '3667.49',
|
|
# e: 'bookTicker',
|
|
# s: 'ETH_USDC',
|
|
# u: 1328288557
|
|
# },
|
|
# stream: 'bookTicker.ETH_USDC'
|
|
# }
|
|
data = self.safe_dict(message, 'data', {})
|
|
marketId = self.safe_string(data, 's')
|
|
market = self.safe_market(marketId)
|
|
symbol = self.safe_symbol(marketId, market)
|
|
parsedBidAsk = self.parse_ws_bid_ask(data, market)
|
|
messageHash = 'bidask' + ':' + symbol
|
|
self.bidsasks[symbol] = parsedBidAsk
|
|
client.resolve(parsedBidAsk, messageHash)
|
|
|
|
def parse_ws_bid_ask(self, ticker, market=None):
|
|
#
|
|
# {
|
|
# A: '0.4087',
|
|
# B: '0.0020',
|
|
# E: '1754517402450016',
|
|
# T: '1754517402449064',
|
|
# a: '3667.50',
|
|
# b: '3667.49',
|
|
# e: 'bookTicker',
|
|
# s: 'ETH_USDC',
|
|
# u: 1328288557
|
|
# }
|
|
#
|
|
marketId = self.safe_string(ticker, 's')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = self.safe_string(market, 'symbol')
|
|
microseconds = self.safe_integer(ticker, 'E')
|
|
timestamp = self.parse_to_int(microseconds / 1000)
|
|
ask = self.safe_string(ticker, 'a')
|
|
askVolume = self.safe_string(ticker, 'A')
|
|
bid = self.safe_string(ticker, 'b')
|
|
bidVolume = self.safe_string(ticker, 'B')
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'ask': ask,
|
|
'askVolume': askVolume,
|
|
'bid': bid,
|
|
'bidVolume': bidVolume,
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
"""
|
|
watches historical candlestick data containing the open, high, low, close price, and the volume of a market
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/K-Line
|
|
|
|
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
|
:param str timeframe: the length of time each candle represents
|
|
:param int [since]: timestamp in ms of the earliest candle to fetch
|
|
:param int [limit]: the maximum amount of candles to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
result = await self.watch_ohlcv_for_symbols([[symbol, timeframe]], since, limit, params)
|
|
return result[symbol][timeframe]
|
|
|
|
async def un_watch_ohlcv(self, symbol: str, timeframe: str = '1m', params={}) -> Any:
|
|
"""
|
|
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/K-Line
|
|
|
|
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
|
:param str timeframe: the length of time each candle represents
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
return await self.un_watch_ohlcv_for_symbols([[symbol, timeframe]], params)
|
|
|
|
async def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
watches historical candlestick data containing the open, high, low, close price, and the volume of a market
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/K-Line
|
|
|
|
:param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
|
|
: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
|
|
"""
|
|
symbolsLength = len(symbolsAndTimeframes)
|
|
if symbolsLength == 0 or not isinstance(symbolsAndTimeframes[0], list):
|
|
raise ArgumentsRequired(self.id + " watchOHLCVForSymbols() requires a an array of symbols and timeframes, like ['ETH/USDC', '1m']")
|
|
await self.load_markets()
|
|
topics = []
|
|
messageHashes = []
|
|
for i in range(0, len(symbolsAndTimeframes)):
|
|
symbolAndTimeframe = symbolsAndTimeframes[i]
|
|
marketId = self.safe_string(symbolAndTimeframe, 0)
|
|
market = self.market(marketId)
|
|
tf = self.safe_string(symbolAndTimeframe, 1)
|
|
interval = self.safe_string(self.timeframes, tf, tf)
|
|
topics.append('kline.' + interval + '.' + market['id'])
|
|
messageHashes.append('candles:' + market['symbol'] + ':' + interval)
|
|
symbol, timeframe, candles = await self.watch_public(topics, messageHashes, params)
|
|
if self.newUpdates:
|
|
limit = candles.getLimit(symbol, limit)
|
|
filtered = self.filter_by_since_limit(candles, since, limit, 0, True)
|
|
return self.create_ohlcv_object(symbol, timeframe, filtered)
|
|
|
|
async def un_watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], params={}) -> Any:
|
|
"""
|
|
unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/K-Line
|
|
|
|
:param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
symbolsLength = len(symbolsAndTimeframes)
|
|
if symbolsLength == 0 or not isinstance(symbolsAndTimeframes[0], list):
|
|
raise ArgumentsRequired(self.id + " unWatchOHLCVForSymbols() requires a an array of symbols and timeframes, like ['ETH/USDC', '1m']")
|
|
await self.load_markets()
|
|
topics = []
|
|
messageHashes = []
|
|
for i in range(0, len(symbolsAndTimeframes)):
|
|
symbolAndTimeframe = symbolsAndTimeframes[i]
|
|
marketId = self.safe_string(symbolAndTimeframe, 0)
|
|
market = self.market(marketId)
|
|
tf = self.safe_string(symbolAndTimeframe, 1)
|
|
interval = self.safe_string(self.timeframes, tf, tf)
|
|
topics.append('kline.' + interval + '.' + market['id'])
|
|
messageHashes.append('unsubscribe:candles:' + market['symbol'] + ':' + interval)
|
|
return await self.watch_public(topics, messageHashes, params, True)
|
|
|
|
def handle_ohlcv(self, client: Client, message):
|
|
#
|
|
# {
|
|
# data: {
|
|
# E: '1754519557526056',
|
|
# T: '2025-08-07T00:00:00',
|
|
# X: False,
|
|
# c: '3680.520000000',
|
|
# e: 'kline',
|
|
# h: '3681.370000000',
|
|
# l: '3667.650000000',
|
|
# n: 255,
|
|
# o: '3670.150000000',
|
|
# s: 'ETH_USDC',
|
|
# t: '2025-08-06T22:00:00',
|
|
# v: '62.2621000'
|
|
# },
|
|
# stream: 'kline.2h.ETH_USDC'
|
|
# }
|
|
#
|
|
data = self.safe_dict(message, 'data', {})
|
|
marketId = self.safe_string(data, 's')
|
|
market = self.market(marketId)
|
|
symbol = market['symbol']
|
|
stream = self.safe_string(message, 'stream')
|
|
parts = stream.split('.')
|
|
timeframe = self.safe_string(parts, 1)
|
|
if not (symbol in self.ohlcvs):
|
|
self.ohlcvs[symbol] = {}
|
|
if not (timeframe in self.ohlcvs[symbol]):
|
|
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
|
|
stored = ArrayCacheByTimestamp(limit)
|
|
self.ohlcvs[symbol][timeframe] = stored
|
|
ohlcv = self.ohlcvs[symbol][timeframe]
|
|
parsed = self.parse_ws_ohlcv(data)
|
|
ohlcv.append(parsed)
|
|
messageHash = 'candles:' + symbol + ':' + timeframe
|
|
client.resolve([symbol, timeframe, ohlcv], messageHash)
|
|
|
|
def parse_ws_ohlcv(self, ohlcv, market=None) -> list:
|
|
#
|
|
# {
|
|
# E: '1754519557526056',
|
|
# T: '2025-08-07T00:00:00',
|
|
# X: False,
|
|
# c: '3680.520000000',
|
|
# e: 'kline',
|
|
# h: '3681.370000000',
|
|
# l: '3667.650000000',
|
|
# n: 255,
|
|
# o: '3670.150000000',
|
|
# s: 'ETH_USDC',
|
|
# t: '2025-08-06T22:00:00',
|
|
# v: '62.2621000'
|
|
# },
|
|
#
|
|
return [
|
|
self.parse8601(self.safe_string(ohlcv, 'T')),
|
|
self.safe_number(ohlcv, 'o'),
|
|
self.safe_number(ohlcv, 'h'),
|
|
self.safe_number(ohlcv, 'l'),
|
|
self.safe_number(ohlcv, 'c'),
|
|
self.safe_number(ohlcv, 'v'),
|
|
]
|
|
|
|
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
watches information on multiple trades made in a market
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/Trade
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
: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 `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
return await self.watch_trades_for_symbols([symbol], since, limit, params)
|
|
|
|
async def un_watch_trades(self, symbol: str, params={}) -> Any:
|
|
"""
|
|
unWatches from the stream channel
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/Trade
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
: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.un_watch_trades_for_symbols([symbol], params)
|
|
|
|
async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
watches information on multiple trades made in a market
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/Trade
|
|
|
|
:param str[] symbols: unified symbol of the market to fetch trades for
|
|
: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 `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
symbolsLength = len(symbols)
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' watchTradesForSymbols() requires a non-empty array of symbols')
|
|
topics = []
|
|
messageHashes = []
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
marketId = self.market_id(symbol)
|
|
topics.append('trade.' + marketId)
|
|
messageHashes.append('trades:' + symbol)
|
|
trades = await self.watch_public(topics, messageHashes, params)
|
|
if self.newUpdates:
|
|
first = self.safe_value(trades, 0)
|
|
tradeSymbol = self.safe_string(first, 'symbol')
|
|
limit = trades.getLimit(tradeSymbol, limit)
|
|
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
|
|
|
|
async def un_watch_trades_for_symbols(self, symbols: List[str], params={}) -> Any:
|
|
"""
|
|
unWatches from the stream channel
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/Trade
|
|
|
|
:param str[] symbols: unified symbol of the market to fetch trades for
|
|
: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)
|
|
symbolsLength = len(symbols)
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' unWatchTradesForSymbols() requires a non-empty array of symbols')
|
|
topics = []
|
|
messageHashes = []
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
marketId = self.market_id(symbol)
|
|
topics.append('trade.' + marketId)
|
|
messageHashes.append('unsubscribe:trades:' + symbol)
|
|
return await self.watch_public(topics, messageHashes, params, True)
|
|
|
|
def handle_trades(self, client: Client, message):
|
|
#
|
|
# {
|
|
# data: {
|
|
# E: '1754601477746429',
|
|
# T: '1754601477744000',
|
|
# a: '5121860761',
|
|
# b: '5121861755',
|
|
# e: 'trade',
|
|
# m: False,
|
|
# p: '3870.25',
|
|
# q: '0.0008',
|
|
# s: 'ETH_USDC_PERP',
|
|
# t: 10782547
|
|
# },
|
|
# stream: 'trade.ETH_USDC_PERP'
|
|
# }
|
|
#
|
|
data = self.safe_dict(message, 'data', {})
|
|
marketId = self.safe_string(data, 's')
|
|
market = self.market(marketId)
|
|
symbol = market['symbol']
|
|
if not (symbol in self.trades):
|
|
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
stored = ArrayCache(limit)
|
|
self.trades[symbol] = stored
|
|
cache = self.trades[symbol]
|
|
trade = self.parse_ws_trade(data, market)
|
|
cache.append(trade)
|
|
messageHash = 'trades:' + symbol
|
|
client.resolve(cache, messageHash)
|
|
client.resolve(cache, 'trades')
|
|
|
|
def parse_ws_trade(self, trade, market=None):
|
|
#
|
|
# {
|
|
# E: '1754601477746429',
|
|
# T: '1754601477744000',
|
|
# a: '5121860761',
|
|
# b: '5121861755',
|
|
# e: 'trade',
|
|
# m: False,
|
|
# p: '3870.25',
|
|
# q: '0.0008',
|
|
# s: 'ETH_USDC_PERP',
|
|
# t: 10782547
|
|
# }
|
|
#
|
|
microseconds = self.safe_integer(trade, 'E')
|
|
timestamp = self.parse_to_int(microseconds / 1000)
|
|
id = self.safe_string(trade, 't')
|
|
marketId = self.safe_string(trade, 's')
|
|
market = self.safe_market(marketId, market)
|
|
isMaker = self.safe_bool(trade, 'm')
|
|
side = 'sell' if isMaker else 'buy'
|
|
takerOrMaker = 'maker' if isMaker else 'taker'
|
|
price = self.safe_string(trade, 'p')
|
|
amount = self.safe_string(trade, 'q')
|
|
orderId = None
|
|
if side == 'buy':
|
|
orderId = self.safe_string(trade, 'b')
|
|
else:
|
|
orderId = self.safe_string(trade, 'a')
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'id': id,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': market['symbol'],
|
|
'order': orderId,
|
|
'type': None,
|
|
'side': side,
|
|
'takerOrMaker': takerOrMaker,
|
|
'price': price,
|
|
'amount': amount,
|
|
'cost': None,
|
|
'fee': {
|
|
'currency': None,
|
|
'cost': None,
|
|
},
|
|
}, market)
|
|
|
|
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://docs.backpack.exchange/#tag/Streams/Public/Depth
|
|
|
|
: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:
|
|
"""
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Public/Depth
|
|
|
|
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
: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.method]: either '/market/level2' or '/spotMarket/level2Depth5' or '/spotMarket/level2Depth50' default is '/market/level2'
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, False)
|
|
marketIds = self.market_ids(symbols)
|
|
messageHashes = []
|
|
topics = []
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
messageHashes.append('orderbook:' + symbol)
|
|
marketId = marketIds[i]
|
|
topic = 'depth.' + marketId
|
|
topics.append(topic)
|
|
orderbook = await self.watch_public(topics, messageHashes, params)
|
|
return orderbook.limit() # todo check if limit is needed
|
|
|
|
async def un_watch_order_book(self, symbol: str, params={}) -> Any:
|
|
"""
|
|
unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
:param str symbol: unified array of symbols
|
|
: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.un_watch_order_book_for_symbols([symbol], params)
|
|
|
|
async def un_watch_order_book_for_symbols(self, symbols: List[str], params={}) -> Any:
|
|
"""
|
|
unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
:param str[] symbols: unified array of symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.method]: either '/market/level2' or '/spotMarket/level2Depth5' or '/spotMarket/level2Depth50' default is '/market/level2'
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, False)
|
|
marketIds = self.market_ids(symbols)
|
|
messageHashes = []
|
|
topics = []
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
messageHashes.append('unsubscribe:orderbook:' + symbol)
|
|
marketId = marketIds[i]
|
|
topic = 'depth.' + marketId
|
|
topics.append(topic)
|
|
return await self.watch_public(topics, messageHashes, params, True)
|
|
|
|
def handle_order_book(self, client: Client, message):
|
|
#
|
|
# initial snapshot is fetched with ccxt's fetchOrderBook
|
|
# the feed does not include a snapshot, just the deltas
|
|
#
|
|
# {
|
|
# "data": {
|
|
# "E": "1754903057555305",
|
|
# "T": "1754903057554352",
|
|
# "U": 1345937436,
|
|
# "a": [],
|
|
# "b": [],
|
|
# "e": "depth",
|
|
# "s": "ETH_USDC",
|
|
# "u": 1345937436
|
|
# },
|
|
# "stream": "depth.ETH_USDC"
|
|
# }
|
|
#
|
|
data = self.safe_dict(message, 'data', {})
|
|
marketId = self.safe_string(data, 's')
|
|
symbol = self.safe_symbol(marketId)
|
|
if not (symbol in self.orderbooks):
|
|
self.orderbooks[symbol] = self.order_book()
|
|
storedOrderBook = self.orderbooks[symbol]
|
|
nonce = self.safe_integer(storedOrderBook, 'nonce')
|
|
deltaNonce = self.safe_integer(data, 'u')
|
|
messageHash = 'orderbook:' + symbol
|
|
if nonce is None:
|
|
cacheLength = len(storedOrderBook.cache)
|
|
# the rest API is very delayed
|
|
# usually it takes at least 9 deltas to resolve
|
|
snapshotDelay = self.handle_option('watchOrderBook', 'snapshotDelay', 10)
|
|
if cacheLength == snapshotDelay:
|
|
self.spawn(self.load_order_book, client, messageHash, symbol, None, {})
|
|
storedOrderBook.cache.append(data)
|
|
return
|
|
elif nonce > deltaNonce:
|
|
return
|
|
self.handle_delta(storedOrderBook, data)
|
|
client.resolve(storedOrderBook, messageHash)
|
|
|
|
def handle_delta(self, orderbook, delta):
|
|
timestamp = self.parse_to_int(self.safe_integer(delta, 'T') / 1000)
|
|
orderbook['timestamp'] = timestamp
|
|
orderbook['datetime'] = self.iso8601(timestamp)
|
|
orderbook['nonce'] = self.safe_integer(delta, 'u')
|
|
bids = self.safe_list(delta, 'b', [])
|
|
asks = self.safe_list(delta, 'a', [])
|
|
storedBids = orderbook['bids']
|
|
storedAsks = orderbook['asks']
|
|
self.handle_bid_asks(storedBids, bids)
|
|
self.handle_bid_asks(storedAsks, asks)
|
|
|
|
def handle_bid_asks(self, bookSide, bidAsks):
|
|
for i in range(0, len(bidAsks)):
|
|
bidAsk = self.parse_bid_ask(bidAsks[i])
|
|
bookSide.storeArray(bidAsk)
|
|
|
|
def get_cache_index(self, orderbook, cache):
|
|
#
|
|
# {"E":"1759338824897386","T":"1759338824895616","U":1662976171,"a":[],"b":[["117357.0","0.00000"]],"e":"depth","s":"BTC_USDC_PERP","u":1662976171}
|
|
firstDelta = self.safe_dict(cache, 0)
|
|
nonce = self.safe_integer(orderbook, 'nonce')
|
|
firstDeltaStart = self.safe_integer(firstDelta, 'U')
|
|
if nonce < firstDeltaStart - 1:
|
|
return -1
|
|
for i in range(0, len(cache)):
|
|
delta = cache[i]
|
|
deltaStart = self.safe_integer(delta, 'U')
|
|
deltaEnd = self.safe_integer(delta, 'u')
|
|
if (nonce >= deltaStart - 1) and (nonce < deltaEnd):
|
|
return i
|
|
return len(cache)
|
|
|
|
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://docs.backpack.exchange/#tag/Streams/Private/Order-update
|
|
|
|
: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
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
topic = 'account.orderUpdate'
|
|
messageHash = 'orders'
|
|
if market is not None:
|
|
topic = 'account.orderUpdate.' + market['id']
|
|
messageHash = 'orders:' + symbol
|
|
orders = await self.watch_private([topic], [messageHash], params)
|
|
if self.newUpdates:
|
|
limit = orders.getLimit(symbol, limit)
|
|
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
|
|
|
|
async def un_watch_orders(self, symbol: Str = None, params={}) -> Any:
|
|
"""
|
|
unWatches information on multiple orders made by the user
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Private/Order-update
|
|
|
|
:param str [symbol]: unified market symbol of the market orders were made in
|
|
: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
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
topic = 'account.orderUpdate'
|
|
messageHash = 'unsubscribe:orders'
|
|
if market is not None:
|
|
topic = 'account.orderUpdate.' + market['id']
|
|
messageHash = 'unsubscribe:orders:' + symbol
|
|
return await self.watch_private([topic], [messageHash], params, True)
|
|
|
|
def handle_order(self, client: Client, message):
|
|
#
|
|
# {
|
|
# data: {
|
|
# E: '1754939110175843',
|
|
# O: 'USER',
|
|
# Q: '4.30',
|
|
# S: 'Bid',
|
|
# T: '1754939110174703',
|
|
# V: 'RejectTaker',
|
|
# X: 'New',
|
|
# Z: '0',
|
|
# e: 'orderAccepted',
|
|
# f: 'GTC',
|
|
# i: '5406825793',
|
|
# o: 'MARKET',
|
|
# q: '0.0010',
|
|
# r: False,
|
|
# s: 'ETH_USDC',
|
|
# t: null,
|
|
# z: '0'
|
|
# },
|
|
# stream: 'account.orderUpdate.ETH_USDC'
|
|
# }
|
|
#
|
|
messageHash = 'orders'
|
|
data = self.safe_dict(message, 'data', {})
|
|
marketId = self.safe_string(data, 's')
|
|
market = self.safe_market(marketId)
|
|
symbol = market['symbol']
|
|
parsed = self.parse_ws_order(data, market)
|
|
orders = self.orders
|
|
if orders is None:
|
|
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
|
|
orders = ArrayCacheBySymbolById(limit)
|
|
self.orders = orders
|
|
orders.append(parsed)
|
|
client.resolve(orders, messageHash)
|
|
symbolSpecificMessageHash = messageHash + ':' + symbol
|
|
client.resolve(orders, symbolSpecificMessageHash)
|
|
|
|
def parse_ws_order(self, order, market=None):
|
|
#
|
|
# {
|
|
# E: '1754939110175879',
|
|
# L: '4299.16',
|
|
# N: 'ETH',
|
|
# O: 'USER',
|
|
# Q: '4.30',
|
|
# S: 'Bid',
|
|
# T: '1754939110174705',
|
|
# V: 'RejectTaker',
|
|
# X: 'Filled',
|
|
# Z: '4.299160',
|
|
# e: 'orderFill',
|
|
# f: 'GTC',
|
|
# i: '5406825793',
|
|
# l: '0.0010',
|
|
# m: False,
|
|
# n: '0.000001',
|
|
# o: 'MARKET',
|
|
# q: '0.0010',
|
|
# r: False,
|
|
# s: 'ETH_USDC',
|
|
# t: 2888471,
|
|
# z: '0.0010'
|
|
# },
|
|
#
|
|
id = self.safe_string(order, 'i')
|
|
clientOrderId = self.safe_string(order, 'c')
|
|
microseconds = self.safe_integer(order, 'E')
|
|
timestamp = self.parse_to_int(microseconds / 1000)
|
|
status = self.parse_ws_order_status(self.safe_string(order, 'X'), market)
|
|
marketId = self.safe_string(order, 's')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
type = self.safe_string_lower(order, 'o')
|
|
timeInForce = self.safe_string(order, 'f')
|
|
side = self.parse_ws_order_side(self.safe_string(order, 'S'))
|
|
price = self.safe_string(order, 'p')
|
|
triggerPrice = self.safe_number(order, 'P')
|
|
amount = self.safe_string(order, 'q')
|
|
cost = self.safe_string(order, 'Z')
|
|
filled = self.safe_string(order, 'l')
|
|
fee = None
|
|
feeCurrency = self.safe_string(order, 'N')
|
|
if feeCurrency is not None:
|
|
fee = {
|
|
'currency': feeCurrency,
|
|
'cost': None,
|
|
}
|
|
return self.safe_order({
|
|
'id': id,
|
|
'clientOrderId': clientOrderId,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': None,
|
|
'status': status,
|
|
'symbol': symbol,
|
|
'type': type,
|
|
'timeInForce': timeInForce,
|
|
'side': side,
|
|
'price': price,
|
|
'stopPrice': None,
|
|
'triggerPrice': triggerPrice,
|
|
'average': None,
|
|
'amount': amount,
|
|
'cost': cost,
|
|
'filled': filled,
|
|
'remaining': None,
|
|
'fee': fee,
|
|
'trades': None,
|
|
'info': order,
|
|
}, market)
|
|
|
|
def parse_ws_order_status(self, status, market=None):
|
|
statuses: dict = {
|
|
'New': 'open',
|
|
'Filled': 'closed',
|
|
'Cancelled': 'canceled',
|
|
'Expired': 'canceled',
|
|
'PartiallyFilled': 'open',
|
|
'TriggerPending': 'open',
|
|
'TriggerFailed': 'rejected',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_ws_order_side(self, side: Str):
|
|
sides: dict = {
|
|
'Bid': 'buy',
|
|
'Ask': 'sell',
|
|
}
|
|
return self.safe_string(sides, side, side)
|
|
|
|
async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
|
|
"""
|
|
watch all open positions
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Private/Position-update
|
|
|
|
:param str[] [symbols]: list of unified market symbols to watch positions for
|
|
:param int [since]: the earliest time in ms to fetch 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()
|
|
symbols = self.market_symbols(symbols)
|
|
messageHashes = []
|
|
topics = []
|
|
if symbols is not None:
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
messageHashes.append('positions' + ':' + symbol)
|
|
topics.append('account.positionUpdate.' + self.market_id(symbol))
|
|
else:
|
|
messageHashes.append('positions')
|
|
topics.append('account.positionUpdate')
|
|
positions = await self.watch_private(topics, messageHashes, params)
|
|
if self.newUpdates:
|
|
return positions
|
|
return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True)
|
|
|
|
async def un_watch_positions(self, symbols: Strings = None, params={}) -> List[Any]:
|
|
"""
|
|
unWatches from the stream channel
|
|
|
|
https://docs.backpack.exchange/#tag/Streams/Private/Position-update
|
|
|
|
:param str[] [symbols]: list of unified market symbols to watch positions for
|
|
: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()
|
|
symbols = self.market_symbols(symbols)
|
|
messageHashes = []
|
|
topics = []
|
|
if symbols is not None:
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
messageHashes.append('unsubscribe:positions' + ':' + symbol)
|
|
topics.append('account.positionUpdate.' + self.market_id(symbol))
|
|
else:
|
|
messageHashes.append('unsubscribe:positions')
|
|
topics.append('account.positionUpdate')
|
|
return await self.watch_private(topics, messageHashes, params, True)
|
|
|
|
def handle_positions(self, client, message):
|
|
#
|
|
# {
|
|
# data: {
|
|
# B: '4236.36',
|
|
# E: '1754943862040486',
|
|
# M: '4235.88650933',
|
|
# P: '-0.000473',
|
|
# Q: '0.0010',
|
|
# T: '1754943862040487',
|
|
# b: '4238.479',
|
|
# e: 'positionOpened',
|
|
# f: '0.02',
|
|
# i: 5411399049,
|
|
# l: '0',
|
|
# m: '0.0125',
|
|
# n: '4.23588650933',
|
|
# p: '0',
|
|
# q: '0.0010',
|
|
# s: 'ETH_USDC_PERP'
|
|
# },
|
|
# stream: 'account.positionUpdate'
|
|
# }
|
|
#
|
|
messageHash = 'positions'
|
|
data = self.safe_dict(message, 'data', {})
|
|
if self.positions is None:
|
|
self.positions = ArrayCacheBySymbolById()
|
|
cache = self.positions
|
|
parsedPosition = self.parse_ws_position(data)
|
|
microseconds = self.safe_integer(data, 'E')
|
|
timestamp = self.parse_to_int(microseconds / 1000)
|
|
parsedPosition['timestamp'] = timestamp
|
|
parsedPosition['datetime'] = self.iso8601(timestamp)
|
|
cache.append(parsedPosition)
|
|
symbolSpecificMessageHash = messageHash + ':' + parsedPosition['symbol']
|
|
client.resolve([parsedPosition], messageHash)
|
|
client.resolve([parsedPosition], symbolSpecificMessageHash)
|
|
|
|
def parse_ws_position(self, position, market=None):
|
|
#
|
|
# {
|
|
# B: '4236.36',
|
|
# E: '1754943862040486',
|
|
# M: '4235.88650933',
|
|
# P: '-0.000473',
|
|
# Q: '0.0010',
|
|
# T: '1754943862040487',
|
|
# b: '4238.479',
|
|
# e: 'positionOpened',
|
|
# f: '0.02',
|
|
# i: 5411399049,
|
|
# l: '0',
|
|
# m: '0.0125',
|
|
# n: '4.23588650933',
|
|
# p: '0',
|
|
# q: '0.0010',
|
|
# s: 'ETH_USDC_PERP'
|
|
# }
|
|
#
|
|
id = self.safe_string(position, 'i')
|
|
marketId = self.safe_string(position, 's')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
notional = self.safe_string(position, 'n')
|
|
liquidationPrice = self.safe_string(position, 'l')
|
|
entryPrice = self.safe_string(position, 'b')
|
|
realizedPnl = self.safe_string(position, 'p')
|
|
unrealisedPnl = self.safe_string(position, 'P')
|
|
contracts = self.safe_string(position, 'Q')
|
|
markPrice = self.safe_string(position, 'M')
|
|
netQuantity = self.safe_number(position, 'q')
|
|
hedged = False
|
|
side = 'long'
|
|
if netQuantity < 0:
|
|
side = 'short'
|
|
if netQuantity is None:
|
|
hedged = None
|
|
side = None
|
|
microseconds = self.safe_integer(position, 'E')
|
|
timestamp = self.parse_to_int(microseconds / 1000)
|
|
maintenanceMarginPercentage = self.safe_number(position, 'm')
|
|
initialMarginPercentage = self.safe_number(position, 'f')
|
|
return self.safe_position({
|
|
'info': position,
|
|
'id': id,
|
|
'symbol': symbol,
|
|
'notional': notional,
|
|
'marginMode': None,
|
|
'liquidationPrice': liquidationPrice,
|
|
'entryPrice': entryPrice,
|
|
'realizedPnl': realizedPnl,
|
|
'unrealizedPnl': unrealisedPnl,
|
|
'percentage': None,
|
|
'contracts': contracts,
|
|
'contractSize': None,
|
|
'markPrice': markPrice,
|
|
'side': side,
|
|
'hedged': hedged,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'maintenanceMargin': None,
|
|
'maintenanceMarginPercentage': maintenanceMarginPercentage,
|
|
'collateral': None,
|
|
'initialMargin': None,
|
|
'initialMarginPercentage': initialMarginPercentage,
|
|
'leverage': None,
|
|
'marginRatio': None,
|
|
})
|
|
|
|
def handle_message(self, client: Client, message):
|
|
if not self.handle_error_message(client, message):
|
|
return
|
|
data = self.safe_dict(message, 'data')
|
|
event = self.safe_string(data, 'e')
|
|
if event == 'ticker':
|
|
self.handle_ticker(client, message)
|
|
elif event == 'bookTicker':
|
|
self.handle_bid_ask(client, message)
|
|
elif event == 'kline':
|
|
self.handle_ohlcv(client, message)
|
|
elif event == 'trade':
|
|
self.handle_trades(client, message)
|
|
elif event == 'depth':
|
|
self.handle_order_book(client, message)
|
|
elif event == 'orderAccepted' or event == 'orderUpdate' or event == 'orderFill' or event == 'orderCancelled' or event == 'orderExpired' or event == 'orderModified' or event == 'triggerPlaced' or event == 'triggerFailed':
|
|
self.handle_order(client, message)
|
|
elif event == 'positionAdjusted' or event == 'positionOpened' or event == 'positionClosed' or event == 'positionUpdated':
|
|
self.handle_positions(client, message)
|
|
|
|
def handle_error_message(self, client: Client, message) -> Bool:
|
|
#
|
|
# {
|
|
# id: null,
|
|
# error: {
|
|
# code: 4006,
|
|
# message: 'Invalid stream'
|
|
# }
|
|
# }
|
|
#
|
|
error = self.safe_dict(message, 'error', {})
|
|
code = self.safe_integer(error, 'code')
|
|
try:
|
|
if code is not None:
|
|
msg = self.safe_string(error, 'message')
|
|
raise ExchangeError(self.id + ' ' + msg)
|
|
return True
|
|
except Exception as e:
|
|
client.reject(e)
|
|
return True
|