# -*- coding: utf-8 -*- # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code import ccxt.async_support from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp from ccxt.base.types import Any, Bool, Int, 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 ArgumentsRequired class grvt(ccxt.async_support.grvt): def describe(self) -> Any: return self.deep_extend(super(grvt, self).describe(), { 'has': { 'ws': True, 'watchTicker': True, 'watchTickers': True, 'watchTrades': True, 'watchTradesForSymbols': True, 'watchOHLCV': True, 'watchOHLCVForSymbols': True, 'watchOrderBook': True, 'watchOrderBookForSymbols': True, 'watchMyTrades': True, 'watchPositions': True, 'watchOrders': True, }, 'urls': { 'api': { 'ws': { 'publicMarket': 'wss://market-data.grvt.io/ws/full', 'privateTrading': 'wss://trades.grvt.io/ws/full', }, }, }, 'options': { 'watchOrderBookForSymbols': { 'depth': 100, # 5, 10, 20, 50, 100 'interval': 500, # 100, 200, 500, 1000 'channel': 'v1.book.s', # v1.book.s | v1.book.d }, 'watchTickers': { 'channel': 'v1.ticker.s', # v1.ticker.s | v1.ticker.d | v1.mini.s | v1.mini.d 'interval': 500, # raw, 50, 100, 200, 500, 1000, 5000 }, }, 'streaming': { 'keepAlive': 300000, # 5 minutes }, }) def handle_message(self, client: Client, message): # # confirmation # # { # jsonrpc: '2.0', # result: { # stream: 'v1.mini.d', # subs: ['BTC_USDT_Perp@500'], # unsubs: [], # num_snapshots: [1], # first_sequence_number: ['1061214'], # latest_sequence_number: ['1061213'] # }, # id: 1, # method: 'subscribe' # } # # ticker # # { # stream: "v1.mini.d", # selector: "BTC_USDT_Perp@500", # sequence_number: "0", # feed: { # event_time: "1767198134519661154", # instrument: "BTC_USDT_Perp", # ... # }, # prev_sequence_number: "0", # } # if self.handle_error_message(client, message): return methods: dict = { 'v1.ticker.s': self.handle_ticker, 'v1.ticker.d': self.handle_ticker, 'v1.mini.d': self.handle_ticker, 'v1.mini.s': self.handle_ticker, 'v1.trade': self.handle_trades, 'v1.candle': self.handle_ohlcv, 'v1.book.s': self.handle_order_book, 'v1.book.d': self.handle_order_book, 'v1.fill': self.handle_my_trade, 'v1.position': self.handle_position, 'v1.order': self.handle_order, } methodName = self.safe_string(message, 'method') if methodName == 'subscribe': # return from confirmation return channel = self.safe_string(message, 'stream') method = self.safe_value(methods, channel) if method is not None: method(client, message) async def subscribe_multiple(self, messageHashes: List[str], request: dict, rawHashes: List[str], publicOrPrivate=True) -> Any: payload: dict = { 'jsonrpc': '2.0', 'method': 'subscribe', 'params': request, 'id': self.request_id(), } apiPart = 'publicMarket' if publicOrPrivate else 'privateTrading' return await self.watch_multiple(self.urls['api']['ws'][apiPart], messageHashes, payload, rawHashes) def request_id(self): self.lock_id() newValue = self.sum(self.safe_integer(self.options, 'requestId', 0), 1) self.options['requestId'] = newValue self.unlock_id() return newValue 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://api-docs.grvt.io/market_data_streams/#mini-ticker-snap-feed-selector :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() symbol = self.symbol(symbol) tickers = await self.watch_tickers([symbol], self.extend(params, {'callerMethodName': 'watchTicker'})) 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://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 ` """ if symbols is None: raise ArgumentsRequired(self.id + ' watchTickers requires a symbols argument') channel = None channel, params = self.handle_option_and_params(params, 'watchTickers', 'channel', 'v1.ticker.s') interval = None interval, params = self.handle_option_and_params(params, 'watchTickers', 'interval', 500) await self.load_markets() symbols = self.market_symbols(symbols) rawHashes = [] messageHashes = [] for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) marketId = market['id'] rawHashes.append(marketId + '@' + str(interval)) messageHashes.append('ticker::' + market['symbol']) request = { 'stream': channel, 'selectors': rawHashes, } ticker = await self.subscribe_multiple(messageHashes, self.extend(params, request), rawHashes) if self.newUpdates: tickers: dict = {} tickers[ticker['symbol']] = ticker return tickers return self.filter_by_array(self.tickers, 'symbol', symbols) def handle_ticker(self, client: Client, message): # # v1.ticker.s # # { # "stream": "v1.ticker.s", # "selector": "BTC_USDT_Perp@500", # "sequence_number": "0", # "feed": { # "event_time": "1767199535382794823", # "instrument": "BTC_USDT_Perp", # "mark_price": "87439.392166151", # "index_price": "87462.426721779", # "last_price": "87467.5", # "last_size": "0.001", # "mid_price": "87474.35", # "best_bid_price": "87474.3", # "best_bid_size": "2.435", # "best_ask_price": "87474.4", # "best_ask_size": "3.825", # "funding_rate_8h_curr": "0.01", # "funding_rate_8h_avg": "0.01", # "interest_rate": "0.0", # "forward_price": "0.0", # "buy_volume_24h_b": "3115.631", # "sell_volume_24h_b": "3195.236", # "buy_volume_24h_q": "275739265.1558", # "sell_volume_24h_q": "282773286.2658", # "high_price": "89187.2", # "low_price": "87404.1", # "open_price": "88667.1", # "open_interest": "1914.093886738", # "long_short_ratio": "1.472050", # "funding_rate": "0.01", # "funding_interval_hours": 8, # "next_funding_time": "1767225600000000000" # }, # "prev_sequence_number": "0" # } # # v1.mini.s # # { # "stream": "v1.mini.s", # "selector": "BTC_USDT_Perp@500", # "sequence_number": "0", # "feed": { # "event_time": "1767198364309454192", # "instrument": "BTC_USDT_Perp", # "mark_price": "87792.25830235", # "index_price": "87806.705713684", # "last_price": "87800.0", # "last_size": "0.032", # "mid_price": "87799.95", # "best_bid_price": "87799.9", # "best_bid_size": "0.151", # "best_ask_price": "87800.0", # "best_ask_size": "5.733" # }, # "prev_sequence_number": "0" # } # # v1.mini.d # # { # "stream": "v1.mini.d", # "selector": "BTC_USDT_Perp@500", # "sequence_number": "1061718", # "feed": { # "event_time": "1767198266500017753", # "instrument": "BTC_USDT_Perp", # "index_price": "87820.929569614", # "best_ask_size": "5.708" # }, # "prev_sequence_number": "1061717" # } # data = self.safe_dict(message, 'feed', {}) selector = self.safe_string(message, 'selector') parts = selector.split('@') marketId = self.safe_string(parts, 0) market = self.safe_market(marketId, None) symbol = market['symbol'] ticker = self.parse_ws_ticker(data, market) self.tickers[symbol] = ticker client.resolve(ticker, 'ticker::' + symbol) def parse_ws_ticker(self, message, market=None): # same dict api return self.parse_ticker(message, market) 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://api-docs.grvt.io/market_data_streams/#trade_1 :param str symbol: unified market symbol of the market trades were made in :param int [since]: the earliest time in ms to fetch orders 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 ` """ return await self.watch_trades_for_symbols([symbol], since, limit, params) async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ get the list of most recent trades for a list of symbols https://api-docs.grvt.io/market_data_streams/#trade_1 :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 :param str [params.limit]: 50, 200, 500, 1000(default 50) :returns dict[]: a list of `trade structures ` """ await self.load_markets() symbols = self.market_symbols(symbols) rawHashes = [] messageHashes = [] for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) marketId = market['id'] limitRaw = self.safe_integer(params, 'limit', 50) # 50, 200, 500, 1000 rawHashes.append(marketId + '@' + str(limitRaw)) messageHashes.append('trade::' + market['symbol']) request = { 'stream': 'v1.trade', 'selectors': rawHashes, } trades = await self.subscribe_multiple(messageHashes, self.extend(params, request), rawHashes) 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) def handle_trades(self, client: Client, message): # # { # "stream": "v1.trade", # "selector": "BTC_USDT_Perp@50", # "sequence_number": "0", # "feed": { # "event_time": "1767257046164798775", # "instrument": "BTC_USDT_Perp", # "is_taker_buyer": True, # "size": "0.001", # "price": "87700.1", # "mark_price": "87700.817100682", # "index_price": "87708.566729268", # "interest_rate": "0.0", # "forward_price": "0.0", # "trade_id": "73808524-19", # "venue": "ORDERBOOK", # "is_rpi": False # }, # "prev_sequence_number": "0" # } # data = self.safe_dict(message, 'feed', {}) selector = self.safe_string(message, 'selector') parts = selector.split('@') marketId = self.safe_string(parts, 0) market = self.safe_market(marketId, None) symbol = market['symbol'] if not (symbol in self.trades): limit = self.safe_integer(self.options, 'tradesLimit', 1000) self.trades[symbol] = ArrayCache(limit) parsed = self.parse_ws_trade(data) stored = self.trades[symbol] stored.append(parsed) client.resolve(stored, 'trade::' + symbol) def parse_ws_trade(self, trade, market=None): # same api return self.parse_trade(trade, market) 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://api-docs.grvt.io/market_data_streams/#candlestick_1 :param str symbol: unified symbol of the market to fetch OHLCV data for :param str timeframe: the length of time each candle represents :param int [since]: timestamp in ms of the earliest candle to fetch :param int [limit]: the maximum amount of candles to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns int[][]: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() symbol = self.symbol(symbol) params['callerMethodName'] = 'watchOHLCV' result = await self.watch_ohlcv_for_symbols([[symbol, timeframe]], since, limit, params) return result[symbol][timeframe] 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, and close price, and the volume of a market https://api-docs.grvt.io/market_data_streams/#candlestick_1 :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 dict: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() rawHashes = [] messageHashes = [] for i in range(0, len(symbolsAndTimeframes)): data = symbolsAndTimeframes[i] symbolString = self.safe_string(data, 0) market = self.market(symbolString) marketId = market['id'] unfiedTimeframe = self.safe_string(data, 1, '1') timeframeId = self.safe_string(self.timeframes, unfiedTimeframe, unfiedTimeframe) rawHashes.append(marketId + '@' + timeframeId + '-TRADE') messageHashes.append('ohlcv::' + market['symbol'] + '::' + unfiedTimeframe) request = { 'stream': 'v1.candle', 'selectors': rawHashes, } symbol, timeframe, stored = await self.subscribe_multiple(messageHashes, self.extend(params, request), rawHashes) if self.newUpdates: limit = stored.getLimit(symbol, limit) filtered = self.filter_by_since_limit(stored, since, limit, 0, True) return self.create_ohlcv_object(symbol, timeframe, filtered) def handle_ohlcv(self, client: Client, message): # # { # "stream": "v1.candle", # "selector": "BTC_USDT_Perp@CI_1_M-TRADE", # "sequence_number": "0", # "feed": { # "open_time": "1767263280000000000", # "close_time": "1767263340000000000", # "open": "87799.1", # "close": "87799.1", # "high": "87799.1", # "low": "87799.1", # "volume_b": "0.0", # "volume_q": "0.0", # "trades": 0, # "instrument": "BTC_USDT_Perp" # }, # "prev_sequence_number": "0" # } # data = self.safe_dict(message, 'feed', {}) selector = self.safe_string(message, 'selector') parts = selector.split('@') marketId = self.safe_string(parts, 0) market = self.safe_market(marketId, None) symbol = market['symbol'] secondPart = self.safe_string(parts, 1) timeframeId = secondPart.replace('-TRADE', '') timeframe = self.find_timeframe(timeframeId) messageHash = 'ohlcv::' + symbol + '::' + timeframe self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {}) if not (timeframe in self.ohlcvs[symbol]): limit = self.handle_option('watchOHLCV', 'limit', 1000) self.ohlcvs[symbol][timeframe] = ArrayCacheByTimestamp(limit) stored = self.ohlcvs[symbol][timeframe] parsed = self.parse_ws_ohlcv(data, market) stored.append(parsed) resolveData = [symbol, timeframe, stored] client.resolve(resolveData, messageHash) def parse_ws_ohlcv(self, ohlcv, market=None) -> list: # same api return self.parse_ohlcv(ohlcv, 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://api-docs.grvt.io/market_data_streams/#orderbook-snap https://api-docs.grvt.io/market_data_streams/#orderbook-delta :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 """ await self.load_markets() symbol = self.symbol(symbol) 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://api-docs.grvt.io/market_data_streams/#orderbook-snap https://api-docs.grvt.io/market_data_streams/#orderbook-delta :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 ` indexed by market symbols """ await self.load_markets() channel = None channel, params = self.handle_option_and_params(params, 'watchOrderBook', 'channel', 'v1.book.d') isSnapshot = channel == 'v1.book.s' symbolsLength = len(symbols) if symbolsLength == 0: raise ArgumentsRequired(self.id + ' watchOrderBookForSymbols() requires a non-empty array of symbols') if limit is None: limit, params = self.handle_option_and_params(params, 'watchOrderBook', 'limit', 100) interval = None interval, params = self.handle_option_and_params(params, 'watchOrderBook', 'interval', 500) symbols = self.market_symbols(symbols) extraPart = str((interval) + '-' + str(limit)) if isSnapshot else str(interval) rawHashes = [] messageHashes = [] for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) marketId = market['id'] rawHashes.append(marketId + '@' + extraPart) messageHashes.append('orderbook::' + market['symbol']) request = { 'stream': channel, 'selectors': rawHashes, } orderbook = await self.subscribe_multiple(messageHashes, self.extend(request, params), rawHashes) return orderbook.limit() def handle_order_book(self, client: Client, message): # # { # "stream": "v1.book.s", # "selector": "BTC_USDT_Perp@500-100", # "sequence_number": "0", # "feed": { # "event_time": "1767292408400000000", # "instrument": "BTC_USDT_Perp", # "bids": [ # { # "price": "88107.3", # "size": "5.322", # "num_orders": 11 # }, # ], # "asks": [ # { # "price": "88107.4", # "size": "5.273", # "num_orders": 37 # }, # ] # }, # "prev_sequence_number": "0" # } # data = self.safe_dict(message, 'feed', {}) selector = self.safe_string(message, 'selector') parts = selector.split('@') marketId = self.safe_string(parts, 0) market = self.safe_market(marketId, None) symbol = market['symbol'] timestamp = self.safe_integer_product(data, 'event_time', 0.000001) if not (symbol in self.orderbooks): self.orderbooks[symbol] = self.order_book() orderbook = self.orderbooks[symbol] sequenceNumber = self.safe_integer(message, 'sequence_number') stream = self.safe_string(message, 'stream') isSnapshotChannel = stream == 'v1.book.s' isSnapshotMessage = sequenceNumber <= 0 if isSnapshotChannel or isSnapshotMessage: snapshot = self.parse_order_book(data, symbol, timestamp, 'bids', 'asks', 'price', 'size') orderbook.reset(snapshot) else: asks = self.safe_list(data, 'asks', []) bids = self.safe_list(data, 'bids', []) self.handle_deltas_with_keys(orderbook['asks'], asks, 'price', 'size') self.handle_deltas_with_keys(orderbook['bids'], bids, 'price', 'size') orderbook['timestamp'] = timestamp orderbook['datetime'] = self.iso8601(timestamp) # grvt defaults to the delta channel(v1.book.d); if the very first # message is a delta, the freshly-created orderbook has symbol=null # because no snapshot has reset it yet. Set it unconditionally — we # know the symbol from the selector regardless of channel. Java's # typed WsOrderBook surfaces self as `"symbol":null` in the output # Python/JS dict-backed orderbooks happen to mask it but the # unconditional assignment is correct for every language. orderbook['symbol'] = symbol orderbook['nonce'] = sequenceNumber messageHash = 'orderbook::' + symbol self.orderbooks[symbol] = orderbook client.resolve(orderbook, messageHash) async def authenticate(self, params={}): self.check_required_credentials() await self.sign_in() wsOptions: dict = self.safe_dict(self.options, 'ws', {}) authenticated = self.safe_string(wsOptions, 'token') if authenticated is None: accountId = self.safe_string(self.options, 'AuthAccountId') cookieValue = self.safe_string(self.options, 'AuthCookieValue') if cookieValue is None or accountId is None: raise AuthenticationError(self.id + ' : at first, you need to authenticate with exchange using signIn() method.') defaultOptions: dict = { 'ws': { 'options': { 'headers': { 'Cookie': cookieValue, 'X-Grvt-Account-Id': accountId, }, }, }, } self.extend_exchange_options(defaultOptions) self.client(self.urls['api']['ws']['privateTrading']) 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://api-docs.grvt.io/trading_streams/#fill :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 :param boolean [params.unifiedMargin]: use unified margin account :returns dict[]: a list of `trade structures ` """ await self.load_markets() await self.authenticate() subAccountId = self.getSubAccountId(params) messageHashes = [] rawHashes = [] if symbol is not None: market = self.market(symbol) rawHashes.append(subAccountId + '-' + market['id']) messageHashes.append('myTrades::' + market['symbol']) else: messageHashes.append('myTrades') rawHashes.append(subAccountId) request = { 'stream': 'v1.fill', 'selectors': rawHashes, } trades = await self.subscribe_multiple(messageHashes, self.extend(request, params), messageHashes, False) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_since_limit(trades, since, limit, 'timestamp', True) def handle_my_trade(self, client: Client, message): # # { # "stream": "v1.fill", # "selector": "2147050003876484-BTC_USDT_Perp", # "sequence_number": "1", # "feed": { # "event_time": "1767354369431470728", # "sub_account_id": "2147050003876484", # "instrument": "BTC_USDT_Perp", # "is_buyer": True, # "is_taker": True, # "size": "0.001", # "price": "89473.4", # "mark_price": "89475.966335827", # "index_price": "89515.016819765", # "interest_rate": "0.0", # "forward_price": "0.0", # "realized_pnl": "0.0", # "fee": "0.040263", # "fee_rate": "0.045", # "trade_id": "74150425-1", # "order_id": "0x0101010503a12f6e000000007791f1bd", # "venue": "ORDERBOOK", # "is_liquidation": False, # "client_order_id": "99191900", # "signer": "0x42c9f56f2c9da534f64b8806d64813b29c62a01d", # "broker": "UNSPECIFIED", # "is_rpi": False, # "builder": "0x00", # "builder_fee_rate": "0.0", # "builder_fee": "0" # }, # "prev_sequence_number": "0" # } # data = self.safe_dict(message, 'feed', {}) if self.myTrades is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) self.myTrades = ArrayCacheBySymbolById(limit) trade = self.parse_ws_my_trade(data) self.myTrades.append(trade) client.resolve(self.myTrades, 'myTrades::' + trade['symbol']) client.resolve(self.myTrades, 'myTrades') def parse_ws_my_trade(self, trade, market=None): return self.parse_trade(trade, market) async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]: """ https://api-docs.grvt.io/trading_streams/#positions watch all open positions :param str[] [symbols]: list of unified market symbols :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.authenticate() await self.load_markets() subAccountId = self.getSubAccountId(params) symbols = self.market_symbols(symbols) rawHashes = [] messageHashes = [] if symbols is not None: for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) rawHashes.append(subAccountId + '-' + market['id']) messageHashes.append('positions::' + market['symbol']) else: messageHashes.append('positions') rawHashes.append(subAccountId) request = { 'stream': 'v1.position', 'selectors': rawHashes, } newPositions = await self.subscribe_multiple(messageHashes, self.extend(request, params), rawHashes, False) if self.newUpdates: return newPositions return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True) def handle_position(self, client, message): # # { # "stream": "v1.position", # "selector": "2147050003876484-BTC_USDT_Perp", # "sequence_number": "0", # "feed": { # "event_time": "1767356959482262748", # "sub_account_id": "2147050003876484", # "instrument": "BTC_USDT_Perp", # "size": "0.001", # "notional": "89.430118", # "entry_price": "89426.4", # "exit_price": "0.0", # "mark_price": "89430.118505969", # "unrealized_pnl": "0.003718", # "realized_pnl": "0.0", # "total_pnl": "0.003718", # "roi": "0.0041", # "quote_index_price": "0.999101105", # "est_liquidation_price": "74347.153505969", # "leverage": "20.0", # "cumulative_fee": "0.040241", # "cumulative_realized_funding_payment": "0.0", # "margin_type": "CROSS" # }, # "prev_sequence_number": "0" # } # if self.positions is None: self.positions = ArrayCacheBySymbolBySide() data = self.safe_dict(message, 'feed') position = self.parse_ws_position(data) symbol = self.safe_string(position, 'symbol') self.positions.append(position) newPositions = [] newPositions.append(position) client.resolve(newPositions, 'positions::' + symbol) client.resolve(newPositions, 'positions') def parse_ws_position(self, position, market=None): # same api return self.parse_position(position, market) async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ watches information on multiple orders made by the user https://api-docs.grvt.io/trading_streams/#order_1-feed-selector :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() await self.authenticate() subAccountId = self.getSubAccountId(params) messageHashes = [] rawHashes = [] if symbol is None: messageHashes.append('orders') rawHashes.append(subAccountId) else: market = self.market(symbol) messageHashes.append('order::' + market['symbol']) rawHashes.append(subAccountId + '-' + market['id']) request = { 'stream': 'v1.order', 'selectors': rawHashes, } orders = await self.subscribe_multiple(messageHashes, self.extend(request, params), rawHashes, False) if self.newUpdates: limit = orders.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True) def handle_order(self, client: Client, message): # # { # "stream": "v1.order", # "selector": "2147050003876484", # "sequence_number": "17", # "feed": { # "order_id": "0x010101050390cd89000000007799a374", # "sub_account_id": "2147050003876484", # "is_market": False, # "time_in_force": "GOOD_TILL_TIME", # "post_only": False, # "reduce_only": False, # "legs": [ # { # "instrument": "BTC_USDT_Perp", # "size": "0.001", # "limit_price": "87443.0", # "is_buying_asset": True # } # ], # "signature": { # "signer": "0x42c9f56f2c9da534f64b8806d64813b29c62a01d", # "r": "0x4d2b96fdf384f9d8f050e3d72327813a7308969d11dba179eaec514c3427f059", # "s": "0x42717bf56091606691a569d612f302ac27e51d41df840ae217dcd0310790cd89", # "v": 28, # "expiration": "1769951360245000000", # "nonce": 747860882, # "chain_id": "0" # }, # "metadata": { # "client_order_id": "747860882", # "create_time": "1767359366686762920", # "trigger": { # "trigger_type": "UNSPECIFIED", # "tpsl": { # "trigger_by": "UNSPECIFIED", # "trigger_price": "0.0", # "close_position": False # } # }, # "broker": "UNSPECIFIED", # "is_position_transfer": False, # "allow_crossing": False # }, # "state": { # "status": "OPEN", # "reject_reason": "UNSPECIFIED", # "book_size": [ # "0.001" # ], # "traded_size": [ # "0.0" # ], # "update_time": "1767359366686762920", # "avg_fill_price": [ # "0.0" # ] # }, # "builder": "0x00", # "builder_fee": "0.0" # }, # "prev_sequence_number": "16" # } # data = self.safe_dict(message, 'feed') if self.orders is None: limit = self.safe_integer(self.options, 'ordersLimit', 1000) self.orders = ArrayCacheBySymbolById(limit) order = self.parse_ws_order(data) self.orders.append(order) client.resolve(self.orders, 'orders') client.resolve(self.orders, 'order::' + order['symbol']) def parse_ws_order(self, order, market=None) -> Order: # same api return self.parse_order(order, market) def handle_error_message(self, client: Client, response) -> Bool: # # { # "jsonrpc": "2.0", # "error": { # "code": 3000, # "message": "Instrument is invalid" # }, # "id": 1, # "method": "subscribe" # } # error = self.safe_dict(response, 'error') errorCode = self.safe_string(error, 'code') if errorCode is not None: body = self.json(response) feedback = self.id + ' ' + body message = self.safe_string(error, 'message') self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback) self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback) self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback) raise ExchangeError(self.id + ' ' + body) return False