Files
beast-trader/dashboard/venv/lib/python3.12/site-packages/ccxt/pro/aster.py

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)