# -*- 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 ` """ 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 ` """ 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 ` """ 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 ` """ 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 ` """ 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 ` """ 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 ` """ 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 ` """ 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 ` """ 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 ` """ 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 ` 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 ` 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 ` 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 ` 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 ` """ 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 ` """ 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 ` """ 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 ` """ 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