1794 lines
87 KiB
Python
1794 lines
87 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
|
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
|
|
|
import ccxt.async_support
|
|
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp
|
|
from ccxt.base.types import Any, Balances, 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 ArgumentsRequired
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class aster(ccxt.async_support.aster):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(aster, self).describe(), {
|
|
'has': {
|
|
'ws': True,
|
|
'watchBalance': True,
|
|
'watchBidsAsks': True,
|
|
'watchMarkPrice': True,
|
|
'watchMarkPrices': True,
|
|
'watchTrades': True,
|
|
'watchTradesForSymbols': True,
|
|
'watchOrders': True,
|
|
'watchOrderBook': True,
|
|
'watchOrderBookForSymbols': True,
|
|
'watchOHLCV': True,
|
|
'watchOHLCVForSymbols': True,
|
|
'watchPositions': True,
|
|
'watchTicker': True,
|
|
'watchTickers': True,
|
|
'watchMyTrades': True,
|
|
'unWatchTicker': True,
|
|
'unWatchTickers': True,
|
|
'unWatchMarkPrice': True,
|
|
'unWatchMarkPrices': True,
|
|
'unWatchBidsAsks': True,
|
|
'unWatchTrades': True,
|
|
'unWatchTradesForSymbols': True,
|
|
'unWatchOrderBook': True,
|
|
'unWatchOrderBookForSymbols': True,
|
|
'unWatchOHLCV': True,
|
|
'unWatchOHLCVForSymbols': True,
|
|
},
|
|
'urls': {
|
|
'api': {
|
|
'ws': {
|
|
'public': {
|
|
'spot': 'wss://sstream.asterdex.com/stream',
|
|
'swap': 'wss://fstream.asterdex.com/stream',
|
|
},
|
|
'private': {
|
|
'spot': 'wss://sstream.asterdex.com/ws',
|
|
'swap': 'wss://fstream.asterdex.com/ws',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'options': {
|
|
'listenKey': {
|
|
'spot': None,
|
|
'swap': None,
|
|
},
|
|
'lastAuthenticatedTime': {
|
|
'spot': 0,
|
|
'swap': 0,
|
|
},
|
|
'listenKeyRefreshRate': {
|
|
'spot': 3600000, # 60 minutes
|
|
'swap': 3600000,
|
|
},
|
|
'watchBalance': {
|
|
'fetchBalanceSnapshot': False, # or True
|
|
'awaitBalanceSnapshot': True, # whether to wait for the balance snapshot before providing updates
|
|
},
|
|
'wallet': 'wb', # wb = wallet balance, cw = cross balance
|
|
'watchPositions': {
|
|
'fetchPositionsSnapshot': True, # or False
|
|
'awaitPositionsSnapshot': True, # whether to wait for the positions snapshot before providing updates
|
|
},
|
|
},
|
|
'streaming': {},
|
|
'exceptions': {},
|
|
})
|
|
|
|
def get_account_type_from_url(self, url: str) -> str:
|
|
if url.find('fstream') > -1:
|
|
return 'swap'
|
|
return 'spot'
|
|
|
|
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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#simplified-ticker-by-symbol
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#compact-tickers-for-all-symbols-in-the-entire-market
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#full-ticker-per-symbol
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#complete-ticker-for-all-trading-pairs-on-the-entire-market
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-mini-ticker-stream
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-mini-tickers-stream
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-ticker-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-tickers-streams
|
|
|
|
: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>`
|
|
"""
|
|
params['callerMethodName'] = 'watchTicker'
|
|
await self.load_markets()
|
|
symbol = self.safe_symbol(symbol)
|
|
tickers = await self.watch_tickers([symbol], params)
|
|
return tickers[symbol]
|
|
|
|
async def un_watch_ticker(self, symbol: str, params={}) -> Any:
|
|
"""
|
|
unWatches a price ticker
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#simplified-ticker-by-symbol
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#compact-tickers-for-all-symbols-in-the-entire-market
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#full-ticker-per-symbol
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#complete-ticker-for-all-trading-pairs-on-the-entire-market
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-mini-ticker-stream
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-mini-tickers-stream
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-ticker-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-tickers-streams
|
|
|
|
: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>`
|
|
"""
|
|
params['callerMethodName'] = 'unWatchTicker'
|
|
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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#compact-tickers-for-all-symbols-in-the-entire-market
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#complete-ticker-for-all-trading-pairs-on-the-entire-market
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-mini-tickers-stream
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-tickers-streams
|
|
|
|
:param str[] symbols: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
firstMarket = self.get_market_from_symbols(symbols)
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
symbolsLength = len(symbols)
|
|
methodName = None
|
|
methodName, params = self.handle_param_string(params, 'callerMethodName', 'watchTickers')
|
|
params = self.omit(params, 'callerMethodName')
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'SUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
}
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
market = self.market(symbol)
|
|
subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@ticker')
|
|
messageHashes.append('ticker:' + market['symbol'])
|
|
newTicker = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
if self.newUpdates:
|
|
result: dict = {}
|
|
result[newTicker['symbol']] = newTicker
|
|
return result
|
|
return self.filter_by_array(self.tickers, 'symbol', symbols)
|
|
|
|
async def un_watch_tickers(self, symbols: Strings = None, params={}) -> Any:
|
|
"""
|
|
unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#compact-tickers-for-all-symbols-in-the-entire-market
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#complete-ticker-for-all-trading-pairs-on-the-entire-market
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-mini-tickers-stream
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-tickers-streams
|
|
|
|
:param str[] symbols: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
firstMarket = self.get_market_from_symbols(symbols)
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
symbolsLength = len(symbols)
|
|
methodName = None
|
|
methodName, params = self.handle_param_string(params, 'callerMethodName', 'unWatchTickers')
|
|
params = self.omit(params, 'callerMethodName')
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'UNSUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
}
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
market = self.market(symbol)
|
|
subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@ticker')
|
|
messageHashes.append('unsubscribe:ticker:' + market['symbol'])
|
|
return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
|
|
async def watch_mark_price(self, symbol: str, params={}) -> Ticker:
|
|
"""
|
|
watches a mark price for a specific market
|
|
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream-for-all-market
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.use1sFreq]: *default is True* if set to True, the mark price will be updated every second, otherwise every 3 seconds
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
|
|
"""
|
|
params['callerMethodName'] = 'watchMarkPrice'
|
|
await self.load_markets()
|
|
symbol = self.safe_symbol(symbol)
|
|
tickers = await self.watch_mark_prices([symbol], params)
|
|
return tickers[symbol]
|
|
|
|
async def un_watch_mark_price(self, symbol: str, params={}) -> Any:
|
|
"""
|
|
unWatches a mark price for a specific market
|
|
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream-for-all-market
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.use1sFreq]: *default is True* if set to True, the mark price will be updated every second, otherwise every 3 seconds
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
|
|
"""
|
|
params['callerMethodName'] = 'unWatchMarkPrice'
|
|
return await self.un_watch_mark_prices([symbol], params)
|
|
|
|
async def watch_mark_prices(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
watches the mark price for all markets
|
|
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream-for-all-market
|
|
|
|
:param str[] symbols: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.use1sFreq]: *default is True* if set to True, the mark price will be updated every second, otherwise every 3 seconds
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
firstMarket = self.get_market_from_symbols(symbols)
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
symbolsLength = len(symbols)
|
|
methodName = None
|
|
methodName, params = self.handle_param_string(params, 'callerMethodName', 'watchMarkPrices')
|
|
params = self.omit(params, 'callerMethodName')
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'SUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
}
|
|
use1sFreq = self.safe_bool(params, 'use1sFreq', True)
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
market = self.market(symbol)
|
|
suffix = '@1s' if (use1sFreq) else ''
|
|
subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@markPrice' + suffix)
|
|
messageHashes.append('ticker:' + market['symbol'])
|
|
newTicker = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
if self.newUpdates:
|
|
result = {}
|
|
result[newTicker['symbol']] = newTicker
|
|
return result
|
|
return self.filter_by_array(self.tickers, 'symbol', symbols)
|
|
|
|
async def un_watch_mark_prices(self, symbols: Strings = None, params={}) -> Any:
|
|
"""
|
|
watches the mark price for all markets
|
|
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream-for-all-market
|
|
|
|
:param str[] symbols: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.use1sFreq]: *default is True* if set to True, the mark price will be updated every second, otherwise every 3 seconds
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
firstMarket = self.get_market_from_symbols(symbols)
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
symbolsLength = len(symbols)
|
|
methodName = None
|
|
methodName, params = self.handle_param_string(params, 'callerMethodName', 'unWatchMarkPrices')
|
|
params = self.omit(params, 'callerMethodName')
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'UNSUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
}
|
|
use1sFreq = self.safe_bool(params, 'use1sFreq', True)
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
market = self.market(symbol)
|
|
suffix = '@1s' if (use1sFreq) else ''
|
|
subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@markPrice' + suffix)
|
|
messageHashes.append('unsubscribe:ticker:' + market['symbol'])
|
|
return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
|
|
def handle_ticker(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "e": "24hrTicker",
|
|
# "E": 1754451187277,
|
|
# "s": "CAKEUSDT",
|
|
# "p": "-0.08800",
|
|
# "P": "-3.361",
|
|
# "w": "2.58095",
|
|
# "c": "2.53000",
|
|
# "Q": "5",
|
|
# "o": "2.61800",
|
|
# "h": "2.64700",
|
|
# "l": "2.52400",
|
|
# "v": "15775",
|
|
# "q": "40714.46000",
|
|
# "O": 1754364780000,
|
|
# "C": 1754451187274,
|
|
# "F": 6571389,
|
|
# "L": 6574507,
|
|
# "n": 3119
|
|
# }
|
|
# {
|
|
# "e": "markPriceUpdate",
|
|
# "E": 1754660466000,
|
|
# "s": "BTCUSDT",
|
|
# "p": "116809.60000000",
|
|
# "P": "116595.54012838",
|
|
# "i": "116836.93534884",
|
|
# "r": "0.00010000",
|
|
# "T": 1754668800000
|
|
# }
|
|
#
|
|
marketType = self.get_account_type_from_url(client.url)
|
|
ticker = message
|
|
parsed = self.parse_ws_ticker(ticker, marketType)
|
|
symbol = parsed['symbol']
|
|
messageHash = 'ticker:' + symbol
|
|
self.tickers[symbol] = parsed
|
|
client.resolve(self.tickers[symbol], messageHash)
|
|
|
|
def parse_ws_ticker(self, message, marketType):
|
|
event = self.safe_string(message, 'e')
|
|
marketId = self.safe_string(message, 's')
|
|
timestamp = self.safe_integer(message, 'E')
|
|
market = self.safe_market(marketId, None, None, marketType)
|
|
last = self.safe_string(message, 'c')
|
|
if event == 'markPriceUpdate':
|
|
return self.safe_ticker({
|
|
'symbol': market['symbol'],
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'info': message,
|
|
'markPrice': self.safe_string(message, 'p'),
|
|
'indexPrice': self.safe_string(message, 'i'),
|
|
})
|
|
return self.safe_ticker({
|
|
'symbol': market['symbol'],
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': self.safe_string(message, 'h'),
|
|
'low': self.safe_string(message, 'l'),
|
|
'bid': None,
|
|
'bidVolume': None,
|
|
'ask': None,
|
|
'askVolume': None,
|
|
'vwap': self.safe_string(message, 'w'),
|
|
'open': self.safe_string(message, 'o'),
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None,
|
|
'change': self.safe_string(message, 'p'),
|
|
'percentage': self.safe_string(message, 'P'),
|
|
'average': None,
|
|
'baseVolume': self.safe_string(message, 'v'),
|
|
'quoteVolume': self.safe_string(message, 'q'),
|
|
'info': message,
|
|
}, market)
|
|
|
|
async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
watches best bid & ask for symbols
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#best-order-book-information-by-symbol
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#best-order-book-information-across-the-entire-market
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-book-ticker-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-book-tickers-stream
|
|
|
|
:param str[] symbols: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
firstMarket = self.get_market_from_symbols(symbols)
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
symbolsLength = len(symbols)
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' watchBidsAsks() requires a non-empty array of symbols')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'SUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
}
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
market = self.market(symbol)
|
|
subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@bookTicker')
|
|
messageHashes.append('bidask:' + market['symbol'])
|
|
newTicker = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
if self.newUpdates:
|
|
result = {}
|
|
result[newTicker['symbol']] = newTicker
|
|
return result
|
|
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
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#best-order-book-information-by-symbol
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#best-order-book-information-across-the-entire-market
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-book-ticker-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-book-tickers-stream
|
|
|
|
:param str[] symbols: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
firstMarket = self.get_market_from_symbols(symbols)
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
symbolsLength = len(symbols)
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' unWatchBidsAsks() requires a non-empty array of symbols')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'UNSUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
}
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
market = self.market(symbol)
|
|
subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@bookTicker')
|
|
messageHashes.append('unsubscribe:bidask:' + market['symbol'])
|
|
return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
|
|
def handle_bid_ask(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "e": "bookTicker",
|
|
# "u": 157240846459,
|
|
# "s": "BTCUSDT",
|
|
# "b": "122046.7",
|
|
# "B": "1.084",
|
|
# "a": "122046.8",
|
|
# "A": "0.001",
|
|
# "T": 1754896692922,
|
|
# "E": 1754896692926
|
|
# }
|
|
#
|
|
marketType = self.get_account_type_from_url(client.url)
|
|
data = message
|
|
marketId = self.safe_string(data, 's')
|
|
market = self.safe_market(marketId, None, None, marketType)
|
|
ticker = self.parse_ws_bid_ask(data, market)
|
|
symbol = ticker['symbol']
|
|
self.bidsasks[symbol] = ticker
|
|
messageHash = 'bidask:' + symbol
|
|
client.resolve(ticker, messageHash)
|
|
|
|
def parse_ws_bid_ask(self, message, market=None):
|
|
timestamp = self.safe_integer(message, 'T')
|
|
return self.safe_ticker({
|
|
'symbol': market['symbol'],
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'ask': self.safe_string(message, 'a'),
|
|
'askVolume': self.safe_string(message, 'A'),
|
|
'bid': self.safe_string(message, 'b'),
|
|
'bidVolume': self.safe_string(message, 'B'),
|
|
'info': 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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#collection-transaction-flow
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#tick-by-tick-trades
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#aggregate-trade-streams
|
|
|
|
:param str symbol: unified market symbol of the market trades were made in
|
|
:param int [since]: the earliest time in ms to fetch trades for
|
|
:param int [limit]: the maximum number of trade structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/?id=trade-structure>`
|
|
"""
|
|
params['callerMethodName'] = 'watchTrades'
|
|
return await self.watch_trades_for_symbols([symbol], since, limit, params)
|
|
|
|
async def un_watch_trades(self, symbol: str, params={}) -> Any:
|
|
"""
|
|
unsubscribe from the trades channel
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#collection-transaction-flow
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#tick-by-tick-trades
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#aggregate-trade-streams
|
|
|
|
:param str symbol: unified market symbol of the market trades were made in
|
|
: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>`
|
|
"""
|
|
params['callerMethodName'] = 'unWatchTrades'
|
|
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]:
|
|
"""
|
|
get the list of most recent trades for a list of symbols
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#collection-transaction-flow
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#tick-by-tick-trades
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#aggregate-trade-streams
|
|
|
|
:param str[] symbols: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
:param int [limit]: the maximum amount of trades to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/?id=public-trades>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
firstMarket = self.get_market_from_symbols(symbols)
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
symbolsLength = len(symbols)
|
|
methodName = None
|
|
methodName, params = self.handle_param_string(params, 'callerMethodName', 'watchTradesForSymbols')
|
|
params = self.omit(params, 'callerMethodName')
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'SUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
'id': 1,
|
|
}
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
market = self.market(symbol)
|
|
marketId = self.safe_string_lower(market, 'id')
|
|
subscriptionArgs.append(marketId + '@aggTrade')
|
|
messageHashes.append('trade::' + market['symbol'])
|
|
trades = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
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:
|
|
"""
|
|
unsubscribe from the trades channel
|
|
|
|
https://github.com/asterdex/api-docs/blob/master/aster-finance-spot-api.md#collection-transaction-flow
|
|
https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api.md#aggregate-trade-streams
|
|
|
|
:param str[] symbols: unified symbol of the market to fetch trades for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/?id=public-trades>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
firstMarket = self.get_market_from_symbols(symbols)
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
symbolsLength = len(symbols)
|
|
methodName = None
|
|
methodName, params = self.handle_param_string(params, 'callerMethodName', 'unWatchTradesForSymbols')
|
|
params = self.omit(params, 'callerMethodName')
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'UNSUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
}
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
market = self.market(symbol)
|
|
subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@aggTrade')
|
|
messageHashes.append('unsubscribe:trade:' + market['symbol'])
|
|
return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
|
|
def handle_trade(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "e": "aggTrade",
|
|
# "E": 1754551358681,
|
|
# "a": 20505890,
|
|
# "s": "BTCUSDT",
|
|
# "p": "114783.7",
|
|
# "q": "0.020",
|
|
# "f": 26024678,
|
|
# "l": 26024682,
|
|
# "T": 1754551358528,
|
|
# "m": False
|
|
# }
|
|
#
|
|
marketType = self.get_account_type_from_url(client.url)
|
|
trade = message
|
|
marketId = self.safe_string(trade, 's')
|
|
market = self.safe_market(marketId, None, None, marketType)
|
|
parsed = self.parse_ws_trade(trade, market)
|
|
symbol = parsed['symbol']
|
|
if not (symbol in self.trades):
|
|
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
self.trades[symbol] = ArrayCache(limit)
|
|
stored = self.trades[symbol]
|
|
stored.append(parsed)
|
|
client.resolve(stored, 'trade::' + symbol)
|
|
|
|
def parse_ws_trade(self, trade, market=None) -> Trade:
|
|
#
|
|
# public watchTrades(spot)
|
|
#
|
|
# {
|
|
# "e": "aggTrade", # Event type
|
|
# "E": 123456789, # Event time
|
|
# "s": "BNBBTC", # Symbol
|
|
# "a": 12345, # Aggregate trade ID
|
|
# "p": "0.001", # Price
|
|
# "q": "100", # Quantity
|
|
# "f": 100, # First trade ID
|
|
# "l": 105, # Last trade ID
|
|
# "T": 123456785, # Trade time
|
|
# "m": True, # Is the buyer the market maker?
|
|
# "M": True # Ignore
|
|
# }
|
|
#
|
|
# private watchMyTrades spot
|
|
#
|
|
# {
|
|
# "e": "executionReport",
|
|
# "E": 1611063861489,
|
|
# "s": "BNBUSDT",
|
|
# "c": "m4M6AD5MF3b1ERe65l4SPq",
|
|
# "S": "BUY",
|
|
# "o": "MARKET",
|
|
# "f": "GTC",
|
|
# "q": "2.00000000",
|
|
# "p": "0.00000000",
|
|
# "P": "0.00000000",
|
|
# "F": "0.00000000",
|
|
# "g": -1,
|
|
# "C": '',
|
|
# "x": "TRADE",
|
|
# "X": "PARTIALLY_FILLED",
|
|
# "r": "NONE",
|
|
# "i": 1296882607,
|
|
# "l": "0.33200000",
|
|
# "z": "0.33200000",
|
|
# "L": "46.86600000",
|
|
# "n": "0.00033200",
|
|
# "N": "BNB",
|
|
# "T": 1611063861488,
|
|
# "t": 109747654,
|
|
# "I": 2696953381,
|
|
# "w": False,
|
|
# "m": False,
|
|
# "M": True,
|
|
# "O": 1611063861488,
|
|
# "Z": "15.55951200",
|
|
# "Y": "15.55951200",
|
|
# "Q": "0.00000000"
|
|
# }
|
|
#
|
|
# private watchMyTrades future/delivery
|
|
#
|
|
# {
|
|
# "s": "BTCUSDT",
|
|
# "c": "pb2jD6ZQHpfzSdUac8VqMK",
|
|
# "S": "SELL",
|
|
# "o": "MARKET",
|
|
# "f": "GTC",
|
|
# "q": "0.001",
|
|
# "p": "0",
|
|
# "ap": "33468.46000",
|
|
# "sp": "0",
|
|
# "x": "TRADE",
|
|
# "X": "FILLED",
|
|
# "i": 13351197194,
|
|
# "l": "0.001",
|
|
# "z": "0.001",
|
|
# "L": "33468.46",
|
|
# "n": "0.00027086",
|
|
# "N": "BNB",
|
|
# "T": 1612095165362,
|
|
# "t": 458032604,
|
|
# "b": "0",
|
|
# "a": "0",
|
|
# "m": False,
|
|
# "R": False,
|
|
# "wt": "CONTRACT_PRICE",
|
|
# "ot": "MARKET",
|
|
# "ps": "BOTH",
|
|
# "cp": False,
|
|
# "rp": "0.00335000",
|
|
# "pP": False,
|
|
# "si": 0,
|
|
# "ss": 0
|
|
# }
|
|
#
|
|
e = self.safe_string(trade, 'e')
|
|
isPublicTrade = (e == 'trade') or (e == 'aggTrade')
|
|
id = self.safe_string_2(trade, 't', 'a')
|
|
timestamp = self.safe_integer(trade, 'T')
|
|
price = self.safe_string_2(trade, 'L', 'p')
|
|
amount = None
|
|
if isPublicTrade:
|
|
amount = self.safe_string(trade, 'q')
|
|
else:
|
|
# private trades, amount is in 'l' field, quantity of the last filled trade
|
|
amount = self.safe_string(trade, 'l')
|
|
cost = self.safe_string(trade, 'Y')
|
|
if cost is None:
|
|
if (price is not None) and (amount is not None):
|
|
cost = Precise.string_mul(price, amount)
|
|
marketId = self.safe_string(trade, 's')
|
|
defaultType = self.safe_string(self.options, 'defaultType', 'spot') if (market is None) else market['type']
|
|
symbol = self.safe_symbol(marketId, market, None, defaultType)
|
|
side = self.safe_string_lower(trade, 'S')
|
|
takerOrMaker = None
|
|
orderId = self.safe_string(trade, 'i')
|
|
if 'm' in trade:
|
|
if side is None:
|
|
side = 'sell' if trade['m'] else 'buy' # self is reversed intentionally
|
|
takerOrMaker = 'maker' if trade['m'] else 'taker'
|
|
fee = None
|
|
feeCost = self.safe_string(trade, 'n')
|
|
if feeCost is not None:
|
|
feeCurrencyId = self.safe_string(trade, 'N')
|
|
feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
|
|
fee = {
|
|
'cost': feeCost,
|
|
'currency': feeCurrencyCode,
|
|
}
|
|
type = self.safe_string_lower(trade, 'o')
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': symbol,
|
|
'id': id,
|
|
'order': orderId,
|
|
'type': type,
|
|
'takerOrMaker': takerOrMaker,
|
|
'side': side,
|
|
'price': price,
|
|
'amount': amount,
|
|
'cost': cost,
|
|
'fee': fee,
|
|
})
|
|
|
|
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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#limited-depth-information
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#incremental-depth-information
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#partial-book-depth-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#diff-book-depth-streams
|
|
|
|
: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
|
|
"""
|
|
params['callerMethodName'] = 'watchOrderBook'
|
|
return await self.watch_order_book_for_symbols([symbol], limit, params)
|
|
|
|
async def un_watch_order_book(self, symbol: str, params={}) -> Any:
|
|
"""
|
|
unsubscribe from the orderbook channel
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#limited-depth-information
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#incremental-depth-information
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#partial-book-depth-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#diff-book-depth-streams
|
|
|
|
:param str symbol: symbol of the market to unwatch the trades for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.limit]: orderbook limit, default is None
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
params['callerMethodName'] = 'unWatchOrderBook'
|
|
return await self.un_watch_order_book_for_symbols([symbol], 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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#limited-depth-information
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#incremental-depth-information
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#partial-book-depth-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#diff-book-depth-streams
|
|
|
|
: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()
|
|
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
firstMarket = self.get_market_from_symbols(symbols)
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
symbolsLength = len(symbols)
|
|
methodName = None
|
|
methodName, params = self.handle_param_string(params, 'callerMethodName', 'watchOrderBookForSymbols')
|
|
params = self.omit(params, 'callerMethodName')
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'SUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
}
|
|
if limit is None or (limit != 5 and limit != 10 and limit != 20):
|
|
limit = 20
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
market = self.market(symbol)
|
|
subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@depth' + str(limit))
|
|
messageHashes.append('orderbook:' + market['symbol'])
|
|
orderbook = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
return orderbook.limit()
|
|
|
|
async def un_watch_order_book_for_symbols(self, symbols: List[str], params={}) -> Any:
|
|
"""
|
|
unsubscribe from the orderbook channel
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#limited-depth-information
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#incremental-depth-information
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#partial-book-depth-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#diff-book-depth-streams
|
|
|
|
:param str[] symbols: unified symbol of the market to unwatch the trades for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.limit]: orderbook limit, default is None
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
firstMarket = self.get_market_from_symbols(symbols)
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
symbolsLength = len(symbols)
|
|
methodName = None
|
|
methodName, params = self.handle_param_string(params, 'callerMethodName', 'unWatchOrderBookForSymbols')
|
|
params = self.omit(params, 'callerMethodName')
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'UNSUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
}
|
|
limit = self.safe_number(params, 'limit')
|
|
params = self.omit(params, 'limit')
|
|
if limit is None or (limit != 5 and limit != 10 and limit != 20):
|
|
limit = 20
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
market = self.market(symbol)
|
|
subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@depth' + limit)
|
|
messageHashes.append('unsubscribe:orderbook:' + market['symbol'])
|
|
return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
|
|
def handle_order_book(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "e": "depthUpdate",
|
|
# "E": 1754556878284,
|
|
# "T": 1754556878031,
|
|
# "s": "BTCUSDT",
|
|
# "U": 156391349814,
|
|
# "u": 156391349814,
|
|
# "pu": 156391348236,
|
|
# "b": [
|
|
# [
|
|
# "114988.3",
|
|
# "0.147"
|
|
# ]
|
|
# ],
|
|
# "a": [
|
|
# [
|
|
# "114988.4",
|
|
# "1.060"
|
|
# ]
|
|
# ]
|
|
# }
|
|
#
|
|
marketType = self.get_account_type_from_url(client.url)
|
|
data = message
|
|
marketId = self.safe_string(data, 's')
|
|
timestamp = self.safe_integer(data, 'T')
|
|
market = self.safe_market(marketId, None, None, marketType)
|
|
symbol = market['symbol']
|
|
if not (symbol in self.orderbooks):
|
|
self.orderbooks[symbol] = self.order_book()
|
|
orderbook = self.orderbooks[symbol]
|
|
snapshot = self.parse_order_book(data, symbol, timestamp, 'b', 'a')
|
|
orderbook.reset(snapshot)
|
|
messageHash = 'orderbook' + ':' + symbol
|
|
self.orderbooks[symbol] = orderbook
|
|
client.resolve(orderbook, messageHash)
|
|
|
|
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, and close price, and the volume of a market
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#k-line-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#klinecandlestick-streams
|
|
|
|
: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
|
|
"""
|
|
params['callerMethodName'] = 'watchOHLCV'
|
|
await self.load_markets()
|
|
symbol = self.safe_symbol(symbol)
|
|
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='1m', params={}) -> Any:
|
|
"""
|
|
unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#k-line-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#klinecandlestick-streams
|
|
|
|
: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
|
|
"""
|
|
params['callerMethodName'] = 'unWatchOHLCV'
|
|
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, and close price, and the volume of a market
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#k-line-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#klinecandlestick-streams
|
|
|
|
: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()
|
|
symbolsLength = len(symbolsAndTimeframes)
|
|
methodName = None
|
|
methodName, params = self.handle_param_string(params, 'callerMethodName', 'watchOHLCVForSymbols')
|
|
params = self.omit(params, 'callerMethodName')
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols')
|
|
symbols = self.get_list_from_object_values(symbolsAndTimeframes, 0)
|
|
marketSymbols = self.market_symbols(symbols, None, False, True, True)
|
|
firstMarket = self.market(marketSymbols[0])
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'SUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
}
|
|
for i in range(0, len(symbolsAndTimeframes)):
|
|
data = symbolsAndTimeframes[i]
|
|
symbolString = self.safe_string(data, 0)
|
|
market = self.market(symbolString)
|
|
symbolString = market['symbol']
|
|
unfiedTimeframe = self.safe_string(data, 1)
|
|
timeframeId = self.safe_string(self.timeframes, unfiedTimeframe, unfiedTimeframe)
|
|
subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@kline_' + timeframeId)
|
|
messageHashes.append('ohlcv:' + market['symbol'] + ':' + unfiedTimeframe)
|
|
symbol, timeframe, stored = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
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)
|
|
|
|
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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#k-line-streams
|
|
https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#klinecandlestick-streams
|
|
|
|
: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
|
|
"""
|
|
await self.load_markets()
|
|
symbolsLength = len(symbolsAndTimeframes)
|
|
methodName = None
|
|
methodName, params = self.handle_param_string(params, 'callerMethodName', 'unWatchOHLCVForSymbols')
|
|
params = self.omit(params, 'callerMethodName')
|
|
if symbolsLength == 0:
|
|
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols')
|
|
symbols = self.get_list_from_object_values(symbolsAndTimeframes, 0)
|
|
marketSymbols = self.market_symbols(symbols, None, False, True, True)
|
|
firstMarket = self.market(marketSymbols[0])
|
|
type = self.safe_string(firstMarket, 'type', 'swap')
|
|
url = self.urls['api']['ws']['public'][type]
|
|
subscriptionArgs = []
|
|
messageHashes = []
|
|
request: dict = {
|
|
'method': 'UNSUBSCRIBE',
|
|
'params': subscriptionArgs,
|
|
}
|
|
for i in range(0, len(symbolsAndTimeframes)):
|
|
data = symbolsAndTimeframes[i]
|
|
symbolString = self.safe_string(data, 0)
|
|
market = self.market(symbolString)
|
|
symbolString = market['symbol']
|
|
unfiedTimeframe = self.safe_string(data, 1)
|
|
timeframeId = self.safe_string(self.timeframes, unfiedTimeframe, unfiedTimeframe)
|
|
subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@kline_' + timeframeId)
|
|
messageHashes.append('unsubscribe:ohlcv:' + market['symbol'] + ':' + unfiedTimeframe)
|
|
return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
|
|
|
|
def handle_ohlcv(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "e": "kline",
|
|
# "E": 1754655777119,
|
|
# "s": "BTCUSDT",
|
|
# "k": {
|
|
# "t": 1754655720000,
|
|
# "T": 1754655779999,
|
|
# "s": "BTCUSDT",
|
|
# "i": "1m",
|
|
# "f": 26032629,
|
|
# "L": 26032629,
|
|
# "o": "116546.9",
|
|
# "c": "116546.9",
|
|
# "h": "116546.9",
|
|
# "l": "116546.9",
|
|
# "v": "0.011",
|
|
# "n": 1,
|
|
# "x": False,
|
|
# "q": "1282.0159",
|
|
# "V": "0.000",
|
|
# "Q": "0.0000",
|
|
# "B": "0"
|
|
# }
|
|
# }
|
|
#
|
|
marketType = self.get_account_type_from_url(client.url)
|
|
data = message
|
|
marketId = self.safe_string(data, 's')
|
|
market = self.safe_market(marketId, None, None, marketType)
|
|
symbol = market['symbol']
|
|
kline = self.safe_dict(data, 'k')
|
|
timeframeId = self.safe_string(kline, 'i')
|
|
timeframe = self.find_timeframe(timeframeId)
|
|
ohlcvsByTimeframe = self.safe_value(self.ohlcvs, symbol)
|
|
if ohlcvsByTimeframe is None:
|
|
self.ohlcvs[symbol] = {}
|
|
if self.safe_value(ohlcvsByTimeframe, timeframe) is None:
|
|
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
|
|
self.ohlcvs[symbol][timeframe] = ArrayCacheByTimestamp(limit)
|
|
stored = self.ohlcvs[symbol][timeframe]
|
|
parsed = self.parse_ws_ohlcv(kline)
|
|
stored.append(parsed)
|
|
messageHash = 'ohlcv:' + symbol + ':' + timeframe
|
|
resolveData = [symbol, timeframe, stored]
|
|
client.resolve(resolveData, messageHash)
|
|
|
|
def parse_ws_ohlcv(self, ohlcv, market=None) -> list:
|
|
return [
|
|
self.safe_integer(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 authenticate(self, type='spot', params={}):
|
|
time = self.milliseconds()
|
|
lastAuthenticatedTimeOptions = self.safe_dict(self.options, 'lastAuthenticatedTime', {})
|
|
lastAuthenticatedTime = self.safe_integer(lastAuthenticatedTimeOptions, type, 0)
|
|
listenKeyRefreshRateOptions = self.safe_dict(self.options, 'listenKeyRefreshRate', {})
|
|
listenKeyRefreshRate = self.safe_integer(listenKeyRefreshRateOptions, type, 3600000) # 1 hour
|
|
if time - lastAuthenticatedTime > listenKeyRefreshRate:
|
|
response = None
|
|
if type == 'spot':
|
|
response = await self.sapiPrivatePostV3ListenKey(params)
|
|
else:
|
|
response = await self.fapiPrivatePostV3ListenKey(params)
|
|
self.options['listenKey'][type] = self.safe_string(response, 'listenKey')
|
|
self.options['lastAuthenticatedTime'][type] = time
|
|
params = self.extend({'type': type}, params)
|
|
self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params)
|
|
|
|
async def keep_alive_listen_key(self, params={}):
|
|
type = self.safe_string(params, 'type', 'spot')
|
|
listenKeyOptions = self.safe_dict(self.options, 'listenKey', {})
|
|
listenKey = self.safe_string(listenKeyOptions, type)
|
|
if listenKey is None:
|
|
return
|
|
try:
|
|
if type == 'spot':
|
|
await self.sapiPrivatePutV3ListenKey() # self.extend the expiry
|
|
else:
|
|
await self.fapiPrivatePutV3ListenKey() # self.extend the expiry
|
|
except Exception as error:
|
|
url = self.urls['api']['ws']['private'][type] + '/' + listenKey
|
|
client = self.client(url)
|
|
messageHashes = list(client.futures.keys())
|
|
for i in range(0, len(messageHashes)):
|
|
messageHash = messageHashes[i]
|
|
client.reject(error, messageHash)
|
|
self.options['listenKey'][type] = None
|
|
self.options['lastAuthenticatedTime'][type] = 0
|
|
return
|
|
# whether or not to schedule another listenKey keepAlive request
|
|
listenKeyRefreshOptions = self.safe_dict(self.options, 'listenKeyRefresh', {})
|
|
listenKeyRefreshRate = self.safe_integer(listenKeyRefreshOptions, 'listenKeyRefreshRate', 3600000)
|
|
self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params)
|
|
|
|
def get_private_url(self, type='spot'):
|
|
listenKeyOptions = self.safe_dict(self.options, 'listenKey', {})
|
|
listenKey = self.safe_string(listenKeyOptions, type)
|
|
url = self.urls['api']['ws']['private'][type] + '/' + listenKey
|
|
return url
|
|
|
|
async def watch_balance(self, params={}) -> Balances:
|
|
"""
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://asterdex.github.io/aster-api-website/spot-v3/websocket-account-info/#payload-account_update
|
|
https://asterdex.github.io/aster-api-website/futures-v3/user-data-streams/#event-balance-and-position-update
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.type]: 'spot' or 'swap', default is 'spot'
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/?id=balance-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
type = None
|
|
type, params = self.handle_market_type_and_params('watchBalance', None, params, type)
|
|
await self.authenticate(type, params)
|
|
url = self.get_private_url(type)
|
|
client = self.client(url)
|
|
self.set_balance_cache(client, type)
|
|
options = self.safe_dict(self.options, 'watchBalance')
|
|
fetchBalanceSnapshot = self.safe_bool(options, 'fetchBalanceSnapshot', False)
|
|
awaitBalanceSnapshot = self.safe_bool(options, 'awaitBalanceSnapshot', True)
|
|
if fetchBalanceSnapshot and awaitBalanceSnapshot:
|
|
await client.future(type + ':fetchBalanceSnapshot')
|
|
messageHash = type + ':balance'
|
|
message = None
|
|
return await self.watch(url, messageHash, message, type)
|
|
|
|
def set_balance_cache(self, client: Client, type):
|
|
if (type in client.subscriptions) and (type in self.balance):
|
|
return
|
|
options = self.safe_value(self.options, 'watchBalance')
|
|
fetchBalanceSnapshot = self.safe_bool(options, 'fetchBalanceSnapshot', False)
|
|
if fetchBalanceSnapshot:
|
|
messageHash = type + ':fetchBalanceSnapshot'
|
|
if not (messageHash in client.futures):
|
|
client.future(messageHash)
|
|
self.spawn(self.load_balance_snapshot, client, messageHash, type)
|
|
else:
|
|
self.balance[type] = {}
|
|
|
|
async def load_balance_snapshot(self, client, messageHash, type):
|
|
params: dict = {
|
|
'type': type,
|
|
}
|
|
response = await self.fetch_balance(params)
|
|
self.balance[type] = self.extend(response, self.safe_value(self.balance, type, {}))
|
|
# don't remove the future from the .futures cache
|
|
if messageHash in client.futures:
|
|
future = client.futures[messageHash]
|
|
future.resolve()
|
|
client.resolve(self.balance[type], type + ':balance')
|
|
|
|
def handle_balance(self, client: Client, message):
|
|
#
|
|
# spot balance update
|
|
# {
|
|
# "B": [
|
|
# {
|
|
# "a": "USDT",
|
|
# "f": "16.29445191",
|
|
# "l": "0"
|
|
# },
|
|
# {
|
|
# "a": "ETH",
|
|
# "f": "0.00199920",
|
|
# "l": "0"
|
|
# }
|
|
# ],
|
|
# "e": "outboundAccountPosition",
|
|
# "T": 1768547778317,
|
|
# "u": 1768547778317,
|
|
# "E": 1768547778321,
|
|
# "m": "ORDER"
|
|
# }
|
|
#
|
|
# swap balance and position update
|
|
# {
|
|
# "e": "ACCOUNT_UPDATE",
|
|
# "T": 1768551627708,
|
|
# "E": 1768551627710,
|
|
# "a": {
|
|
# "B": [
|
|
# {
|
|
# "a": "USDT",
|
|
# "wb": "39.41184271",
|
|
# "cw": "39.41184271",
|
|
# "bc": "0"
|
|
# }
|
|
# ],
|
|
# "P": [
|
|
# {
|
|
# "s": "ETHUSDT",
|
|
# "pa": "0",
|
|
# "ep": "0.00000000",
|
|
# "cr": "-0.59070000",
|
|
# "up": "0",
|
|
# "mt": "isolated",
|
|
# "iw": "0",
|
|
# "ps": "BOTH",
|
|
# "ma": "USDT"
|
|
# }
|
|
# ],
|
|
# "m": "ORDER"
|
|
# }
|
|
# }
|
|
#
|
|
accountType = self.get_account_type_from_url(client.url)
|
|
messageHash = accountType + ':balance'
|
|
if self.balance[accountType] is None:
|
|
self.balance[accountType] = {}
|
|
self.balance[accountType]['info'] = message
|
|
message = self.safe_dict(message, 'a', message)
|
|
B = self.safe_list(message, 'B', [])
|
|
wallet = self.safe_string(self.options, 'wallet', 'wb')
|
|
for i in range(0, len(B)):
|
|
entry = B[i]
|
|
currencyId = self.safe_string(entry, 'a')
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['free'] = self.safe_string(entry, 'f')
|
|
account['used'] = self.safe_string(entry, 'l')
|
|
account['total'] = self.safe_string(entry, wallet)
|
|
self.balance[accountType][code] = account
|
|
timestamp = self.safe_integer(message, 'E')
|
|
self.balance[accountType]['timestamp'] = timestamp
|
|
self.balance[accountType]['datetime'] = self.iso8601(timestamp)
|
|
self.balance[accountType] = self.safe_balance(self.balance[accountType])
|
|
client.resolve(self.balance[accountType], messageHash)
|
|
|
|
async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
|
|
"""
|
|
watch all open positions
|
|
|
|
https://asterdex.github.io/aster-api-website/futures-v3/user-data-streams/#event-balance-and-position-update
|
|
|
|
:param str[]|None symbols: list of unified market symbols
|
|
:param number [since]: since timestamp
|
|
:param number [limit]: limit
|
|
:param dict params: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
type = 'swap'
|
|
await self.authenticate(type, params)
|
|
url = self.get_private_url(type)
|
|
client = self.client(url)
|
|
self.set_positions_cache(client)
|
|
messageHashes = []
|
|
messageHash = 'positions'
|
|
symbols = self.market_symbols(symbols, 'swap', True, True)
|
|
if symbols is None:
|
|
messageHashes.append(messageHash)
|
|
else:
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
messageHashes.append(messageHash + '::' + symbol)
|
|
fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True)
|
|
awaitPositionsSnapshot = self.handle_option('watchPositions', 'awaitPositionsSnapshot', True)
|
|
cache = self.positions
|
|
if fetchPositionsSnapshot and awaitPositionsSnapshot and cache is None:
|
|
snapshot = await client.future('fetchPositionsSnapshot')
|
|
return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True)
|
|
newPositions = await self.watch_multiple(url, messageHashes, None, [type])
|
|
if self.newUpdates:
|
|
return newPositions
|
|
return self.filter_by_symbols_since_limit(cache, symbols, since, limit, True)
|
|
|
|
def set_positions_cache(self, client: Client):
|
|
if self.positions is not None:
|
|
return
|
|
fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', False)
|
|
if fetchPositionsSnapshot:
|
|
messageHash = 'fetchPositionsSnapshot'
|
|
if not (messageHash in client.futures):
|
|
client.future(messageHash)
|
|
self.spawn(self.load_positions_snapshot, client, messageHash)
|
|
else:
|
|
self.positions = ArrayCacheBySymbolBySide()
|
|
|
|
async def load_positions_snapshot(self, client, messageHash):
|
|
positions = await self.fetch_positions()
|
|
self.positions = ArrayCacheBySymbolBySide()
|
|
cache = self.positions
|
|
for i in range(0, len(positions)):
|
|
position = positions[i]
|
|
contracts = self.safe_number(position, 'contracts', 0)
|
|
if contracts > 0:
|
|
cache.append(position)
|
|
# don't remove the future from the .futures cache
|
|
if messageHash in client.futures:
|
|
future = client.futures[messageHash]
|
|
future.resolve(cache)
|
|
client.resolve(cache, 'positions')
|
|
|
|
def handle_positions(self, client, message):
|
|
#
|
|
# {
|
|
# "e": "ACCOUNT_UPDATE",
|
|
# "T": 1768551627708,
|
|
# "E": 1768551627710,
|
|
# "a": {
|
|
# "B": [
|
|
# {
|
|
# "a": "USDT",
|
|
# "wb": "39.41184271",
|
|
# "cw": "39.41184271",
|
|
# "bc": "0"
|
|
# }
|
|
# ],
|
|
# "P": [
|
|
# {
|
|
# "s": "ETHUSDT",
|
|
# "pa": "0",
|
|
# "ep": "0.00000000",
|
|
# "cr": "-0.59070000",
|
|
# "up": "0",
|
|
# "mt": "isolated",
|
|
# "iw": "0",
|
|
# "ps": "BOTH",
|
|
# "ma": "USDT"
|
|
# }
|
|
# ],
|
|
# "m": "ORDER"
|
|
# }
|
|
# }
|
|
#
|
|
messageHash = 'positions'
|
|
if self.positions is None:
|
|
self.positions = ArrayCacheBySymbolBySide()
|
|
cache = self.positions
|
|
data = self.safe_dict(message, 'a', {})
|
|
rawPositions = self.safe_list(data, 'P', [])
|
|
newPositions = []
|
|
for i in range(0, len(rawPositions)):
|
|
rawPosition = rawPositions[i]
|
|
position = self.parse_ws_position(rawPosition)
|
|
timestamp = self.safe_integer(message, 'E')
|
|
position['timestamp'] = timestamp
|
|
position['datetime'] = self.iso8601(timestamp)
|
|
newPositions.append(position)
|
|
cache.append(position)
|
|
messageHashes = self.find_message_hashes(client, messageHash)
|
|
if not self.is_empty(messageHashes):
|
|
for i in range(0, len(newPositions)):
|
|
position = newPositions[i]
|
|
symbol = position['symbol']
|
|
symbolMessageHash = messageHash + '::' + symbol
|
|
client.resolve(position, symbolMessageHash)
|
|
client.resolve(newPositions, 'positions')
|
|
|
|
def parse_ws_position(self, position, market=None):
|
|
#
|
|
# {
|
|
# "s": "BTCUSDT", # Symbol
|
|
# "pa": "0", # Position Amount
|
|
# "ep": "0.00000", # Entry Price
|
|
# "cr": "200", #(Pre-fee) Accumulated Realized
|
|
# "up": "0", # Unrealized PnL
|
|
# "mt": "isolated", # Margin Type
|
|
# "iw": "0.00000000", # Isolated Wallet(if isolated position)
|
|
# "ps": "BOTH" # Position Side
|
|
# }
|
|
#
|
|
marketId = self.safe_string(position, 's')
|
|
contracts = self.safe_string(position, 'pa')
|
|
contractsAbs = Precise.string_abs(self.safe_string(position, 'pa'))
|
|
positionSide = self.safe_string_lower(position, 'ps')
|
|
hedged = True
|
|
if positionSide == 'both':
|
|
hedged = False
|
|
if not Precise.string_eq(contracts, '0'):
|
|
if Precise.string_lt(contracts, '0'):
|
|
positionSide = 'short'
|
|
else:
|
|
positionSide = 'long'
|
|
return self.safe_position({
|
|
'info': position,
|
|
'id': None,
|
|
'symbol': self.safe_symbol(marketId, None, None, 'swap'),
|
|
'notional': None,
|
|
'marginMode': self.safe_string(position, 'mt'),
|
|
'liquidationPrice': None,
|
|
'entryPrice': self.safe_number(position, 'ep'),
|
|
'unrealizedPnl': self.safe_number(position, 'up'),
|
|
'percentage': None,
|
|
'contracts': self.parse_number(contractsAbs),
|
|
'contractSize': None,
|
|
'markPrice': None,
|
|
'side': positionSide,
|
|
'hedged': hedged,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'maintenanceMargin': None,
|
|
'maintenanceMarginPercentage': None,
|
|
'collateral': None,
|
|
'initialMargin': None,
|
|
'initialMarginPercentage': None,
|
|
'leverage': None,
|
|
'marginRatio': None,
|
|
})
|
|
|
|
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://asterdex.github.io/aster-api-website/spot-v3/websocket-account-info/#payload-order-update
|
|
https://asterdex.github.io/aster-api-website/futures-v3/user-data-streams/#event-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
|
|
:param str [params.type]: 'spot' or 'swap', default is 'spot' if symbol is not provided
|
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
messageHash = 'orders'
|
|
type = None
|
|
type, params = self.handle_market_type_and_params('watchOrders', market, params, type)
|
|
await self.authenticate(type, params)
|
|
if market is not None:
|
|
messageHash += '::' + symbol
|
|
url = self.get_private_url(type)
|
|
client = self.client(url)
|
|
self.set_balance_cache(client, type)
|
|
orders = await self.watch_multiple(url, [messageHash], None, [type])
|
|
if self.newUpdates:
|
|
limit = orders.getLimit(symbol, limit)
|
|
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
|
|
|
|
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://asterdex.github.io/aster-api-website/spot-v3/websocket-account-info/#payload-order-update
|
|
https://asterdex.github.io/aster-api-website/futures-v3/user-data-streams/#event-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
|
|
:param str [params.type]: 'spot' or 'swap', default is 'spot' if symbol is not provided
|
|
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/?id=trade-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
messageHash = 'myTrades'
|
|
type = None
|
|
type, params = self.handle_market_type_and_params('watchOrders', market, params, type)
|
|
await self.authenticate(type, params)
|
|
if market is not None:
|
|
messageHash += '::' + symbol
|
|
url = self.get_private_url(type)
|
|
client = self.client(url)
|
|
self.set_balance_cache(client, type)
|
|
trades = await self.watch_multiple(url, [messageHash], None, [type])
|
|
if self.newUpdates:
|
|
limit = trades.getLimit(symbol, limit)
|
|
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
|
|
|
|
def handle_order_update(self, client: Client, message):
|
|
rawOrder = self.safe_dict(message, 'o', message)
|
|
e = self.safe_string(message, 'e')
|
|
if (e == 'ORDER_TRADE_UPDATE') or (e == 'ALGO_UPDATE'):
|
|
message = self.safe_dict(message, 'o', message)
|
|
self.handle_order(client, rawOrder)
|
|
self.handle_my_trade(client, message)
|
|
|
|
def handle_my_trade(self, client: Client, message):
|
|
messageHash = 'myTrades'
|
|
executionType = self.safe_string(message, 'x')
|
|
if executionType == 'TRADE':
|
|
isSwap = client.url.find('fstream') >= 0
|
|
type = 'swap' if isSwap else 'spot'
|
|
fakeMarket = self.safe_market_structure({'type': type})
|
|
trade = self.parse_ws_trade(message, fakeMarket)
|
|
orderId = self.safe_string(trade, 'order')
|
|
tradeFee = self.safe_dict(trade, 'fee', {})
|
|
tradeFee = self.extend({}, tradeFee)
|
|
symbol = self.safe_string(trade, 'symbol')
|
|
if orderId is not None and tradeFee is not None and symbol is not None:
|
|
cachedOrders = self.orders
|
|
if cachedOrders is not None:
|
|
orders = self.safe_value(cachedOrders.hashmap, symbol, {})
|
|
order = self.safe_value(orders, orderId)
|
|
if order is not None:
|
|
# accumulate order fees
|
|
fees = self.safe_value(order, 'fees')
|
|
fee = self.safe_value(order, 'fee')
|
|
if not self.is_empty(fees):
|
|
insertNewFeeCurrency = True
|
|
for i in range(0, len(fees)):
|
|
orderFee = fees[i]
|
|
if orderFee['currency'] == tradeFee['currency']:
|
|
feeCost = self.sum(tradeFee['cost'], orderFee['cost'])
|
|
order['fees'][i]['cost'] = float(self.currency_to_precision(tradeFee['currency'], feeCost))
|
|
insertNewFeeCurrency = False
|
|
break
|
|
if insertNewFeeCurrency:
|
|
order['fees'].append(tradeFee)
|
|
elif fee is not None:
|
|
if fee['currency'] == tradeFee['currency']:
|
|
feeCost = self.sum(fee['cost'], tradeFee['cost'])
|
|
order['fee']['cost'] = float(self.currency_to_precision(tradeFee['currency'], feeCost))
|
|
elif fee['currency'] is None:
|
|
order['fee'] = tradeFee
|
|
else:
|
|
order['fees'] = [fee, tradeFee]
|
|
order['fee'] = None
|
|
else:
|
|
order['fee'] = tradeFee
|
|
# save self trade in the order
|
|
orderTrades = self.safe_list(order, 'trades', [])
|
|
orderTrades.append(trade)
|
|
order['trades'] = orderTrades
|
|
# don't append twice cause it breaks newUpdates mode
|
|
# self order already exists in the cache
|
|
if self.myTrades is None:
|
|
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
self.myTrades = ArrayCacheBySymbolById(limit)
|
|
myTrades = self.myTrades
|
|
myTrades.append(trade)
|
|
client.resolve(self.myTrades, messageHash)
|
|
messageHashSymbol = messageHash + '::' + symbol
|
|
client.resolve(self.myTrades, messageHashSymbol)
|
|
|
|
def handle_order(self, client: Client, message):
|
|
#
|
|
# spot
|
|
# {
|
|
# "e": "executionReport", # Event type
|
|
# "E": 1499405658658, # Event time
|
|
# "s": "ETHBTC", # Symbol
|
|
# "c": "mUvoqJxFIILMdfAW5iGSOW", # Client order ID
|
|
# "S": "BUY", # Side
|
|
# "o": "LIMIT", # Order type
|
|
# "f": "GTC", # Time in force
|
|
# "q": "1.00000000", # Order quantity
|
|
# "p": "0.10264410", # Order price
|
|
# "P": "0.00000000", # Stop price
|
|
# "F": "0.00000000", # Iceberg quantity
|
|
# "g": -1, # OrderListId
|
|
# "C": null, # Original client order ID; This is the ID of the order being canceled
|
|
# "x": "NEW", # Current execution type
|
|
# "X": "NEW", # Current order status
|
|
# "r": "NONE", # Order reject reason; will be an error code.
|
|
# "i": 4293153, # Order ID
|
|
# "l": "0.00000000", # Last executed quantity
|
|
# "z": "0.00000000", # Cumulative filled quantity
|
|
# "L": "0.00000000", # Last executed price
|
|
# "n": "0", # Commission amount
|
|
# "N": null, # Commission asset
|
|
# "T": 1499405658657, # Transaction time
|
|
# "t": -1, # Trade ID
|
|
# "I": 8641984, # Ignore
|
|
# "w": True, # Is the order on the book?
|
|
# "m": False, # Is self trade the maker side?
|
|
# "M": False, # Ignore
|
|
# "O": 1499405658657, # Order creation time
|
|
# "Z": "0.00000000", # Cumulative quote asset transacted quantity
|
|
# "Y": "0.00000000" # Last quote asset transacted quantity(i.e. lastPrice * lastQty),
|
|
# "Q": "0.00000000" # Quote Order Qty
|
|
# }
|
|
#
|
|
# swap
|
|
# {
|
|
# "s":"BTCUSDT", # Symbol
|
|
# "c":"TEST", # Client Order Id
|
|
# # special client order id:
|
|
# # starts with "autoclose-": liquidation order
|
|
# # "adl_autoclose": ADL auto close order
|
|
# "S":"SELL", # Side
|
|
# "o":"TRAILING_STOP_MARKET", # Order Type
|
|
# "f":"GTC", # Time in Force
|
|
# "q":"0.001", # Original Quantity
|
|
# "p":"0", # Original Price
|
|
# "ap":"0", # Average Price
|
|
# "sp":"7103.04", # Stop Price. Please ignore with TRAILING_STOP_MARKET order
|
|
# "x":"NEW", # Execution Type
|
|
# "X":"NEW", # Order Status
|
|
# "i":8886774, # Order Id
|
|
# "l":"0", # Order Last Filled Quantity
|
|
# "z":"0", # Order Filled Accumulated Quantity
|
|
# "L":"0", # Last Filled Price
|
|
# "N":"USDT", # Commission Asset, will not push if no commission
|
|
# "n":"0", # Commission, will not push if no commission
|
|
# "T":1568879465651, # Order Trade Time
|
|
# "t":0, # Trade Id
|
|
# "b":"0", # Bids Notional
|
|
# "a":"9.91", # Ask Notional
|
|
# "m":false, # Is self trade the maker side?
|
|
# "R":false, # Is self reduce only
|
|
# "wt":"CONTRACT_PRICE", # Stop Price Working Type
|
|
# "ot":"TRAILING_STOP_MARKET", # Original Order Type
|
|
# "ps":"LONG", # Position Side
|
|
# "cp":false, # If Close-All, pushed with conditional order
|
|
# "AP":"7476.89", # Activation Price, only puhed with TRAILING_STOP_MARKET order
|
|
# "cr":"5.0", # Callback Rate, only puhed with TRAILING_STOP_MARKET order
|
|
# "rp":"0" # Realized Profit of the trade
|
|
# }
|
|
#
|
|
messageHash = 'orders'
|
|
market = self.get_market_from_order(client, message)
|
|
if self.orders is None:
|
|
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
|
|
self.orders = ArrayCacheBySymbolById(limit)
|
|
cache = self.orders
|
|
parsed = self.parse_ws_order(message, market)
|
|
symbol = market['symbol']
|
|
cache.append(parsed)
|
|
messageHashes = self.find_message_hashes(client, messageHash)
|
|
if not self.is_empty(messageHashes):
|
|
symbolMessageHash = messageHash + '::' + symbol
|
|
client.resolve(cache, symbolMessageHash)
|
|
client.resolve(cache, messageHash)
|
|
|
|
def parse_ws_order(self, order, market=None):
|
|
executionType = self.safe_string(order, 'x')
|
|
marketId = self.safe_string(order, 's')
|
|
market = self.safe_market(marketId, market)
|
|
timestamp = self.safe_integer(order, 'O')
|
|
T = self.safe_integer(order, 'T')
|
|
lastTradeTimestamp = None
|
|
if executionType == 'NEW' or executionType == 'AMENDMENT' or executionType == 'CANCELED':
|
|
if timestamp is None:
|
|
timestamp = T
|
|
elif executionType == 'TRADE':
|
|
lastTradeTimestamp = T
|
|
lastUpdateTimestamp = T
|
|
fee = None
|
|
feeCost = self.safe_string(order, 'n')
|
|
if (feeCost is not None) and (Precise.string_gt(feeCost, '0')):
|
|
feeCurrencyId = self.safe_string(order, 'N')
|
|
feeCurrency = self.safe_currency_code(feeCurrencyId)
|
|
fee = {
|
|
'cost': feeCost,
|
|
'currency': feeCurrency,
|
|
}
|
|
rawStatus = self.safe_string(order, 'X')
|
|
status = self.parse_order_status(rawStatus)
|
|
clientOrderId = self.safe_string_2(order, 'C', 'caid')
|
|
if (clientOrderId is None) or (len(clientOrderId) == 0):
|
|
clientOrderId = self.safe_string(order, 'c')
|
|
stopPrice = self.safe_string_n(order, ['P', 'sp', 'tp'])
|
|
timeInForce = self.safe_string(order, 'f')
|
|
if timeInForce == 'GTX':
|
|
# GTX means "Good Till Crossing" and is an equivalent way of saying Post Only
|
|
timeInForce = 'PO'
|
|
return self.safe_order({
|
|
'info': order,
|
|
'symbol': market['symbol'],
|
|
'id': self.safe_string_2(order, 'i', 'aid'),
|
|
'clientOrderId': clientOrderId,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': lastTradeTimestamp,
|
|
'lastUpdateTimestamp': lastUpdateTimestamp,
|
|
'type': self.parseOrderType(self.safe_string_lower(order, 'o')),
|
|
'timeInForce': timeInForce,
|
|
'postOnly': None,
|
|
'reduceOnly': self.safe_bool(order, 'R'),
|
|
'side': self.safe_string_lower(order, 'S'),
|
|
'price': self.safe_string(order, 'p'),
|
|
'stopPrice': stopPrice,
|
|
'triggerPrice': stopPrice,
|
|
'amount': self.safe_string(order, 'q'),
|
|
'cost': self.safe_string(order, 'Z'),
|
|
'average': self.safe_string(order, 'ap'),
|
|
'filled': self.safe_string(order, 'z'),
|
|
'remaining': None,
|
|
'status': status,
|
|
'fee': fee,
|
|
'trades': None,
|
|
})
|
|
|
|
def get_market_from_order(self, client: Client, order):
|
|
marketId = self.safe_string(order, 's')
|
|
marketType = self.get_account_type_from_url(client.url)
|
|
return self.safe_market(marketId, None, None, marketType)
|
|
|
|
def handle_balance_and_position(self, client: Client, message):
|
|
self.handle_balance(client, message)
|
|
self.handle_positions(client, message)
|
|
|
|
def handle_message(self, client: Client, message):
|
|
messageInner = self.safe_dict(message, 'data', message) # can be either wrapped in 'data' or full object itself
|
|
event = self.safe_string(messageInner, 'e')
|
|
methods: dict = {
|
|
'24hrTicker': self.handle_ticker,
|
|
'aggTrade': self.handle_trade,
|
|
'depthUpdate': self.handle_order_book,
|
|
'kline': self.handle_ohlcv,
|
|
'markPriceUpdate': self.handle_ticker,
|
|
'bookTicker': self.handle_bid_ask,
|
|
'outboundAccountPosition': self.handle_balance,
|
|
'ACCOUNT_UPDATE': self.handle_balance_and_position,
|
|
'executionReport': self.handle_order_update,
|
|
'ORDER_TRADE_UPDATE': self.handle_order_update,
|
|
}
|
|
method = self.safe_value(methods, event)
|
|
if method is not None:
|
|
method(client, messageInner)
|