Initial commit: 首次建仓,建立目录结构
This commit is contained in:
900
dashboard/venv/lib/python3.12/site-packages/ccxt/pro/grvt.py
Normal file
900
dashboard/venv/lib/python3.12/site-packages/ccxt/pro/grvt.py
Normal file
@ -0,0 +1,900 @@
|
||||
# -*- 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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=trade-structure>`
|
||||
"""
|
||||
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 <https://docs.ccxt.com/?id=public-trades>`
|
||||
"""
|
||||
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 <https://docs.ccxt.com/?id=order-book-structure>` 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 <https://docs.ccxt.com/?id=order-book-structure>` 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 <https://docs.ccxt.com/?id=trade-structure>`
|
||||
"""
|
||||
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 <https://docs.ccxt.com/en/latest/manual.html#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 <https://docs.ccxt.com/?id=order-structure>`
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user