348 lines
15 KiB
Python
348 lines
15 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, ArrayCacheBySymbolBySide
|
|
from ccxt.base.types import Any, Int, OrderBook, Position, Strings, Trade
|
|
from ccxt.async_support.base.ws.client import Client
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import AuthenticationError
|
|
|
|
|
|
class aftermath(ccxt.async_support.aftermath):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(aftermath, self).describe(), {
|
|
'has': {
|
|
'ws': True,
|
|
'watchBalance': False,
|
|
'watchMyTrades': False,
|
|
'watchOHLCV': False,
|
|
'watchOrderBook': True,
|
|
'watchOrders': False,
|
|
'watchTicker': False,
|
|
'watchTickers': False,
|
|
'watchBidsAsks': False,
|
|
'watchTrades': True,
|
|
'watchTradesForSymbols': False,
|
|
'watchPositions': True,
|
|
},
|
|
'urls': {
|
|
'api': {
|
|
'ws': {
|
|
'swap': 'wss://aftermath.finance/iperps-api/ccxt/stream',
|
|
},
|
|
},
|
|
'test': {
|
|
'ws': {
|
|
'swap': 'wss://testnet.aftermath.finance/iperps-api/ccxt/stream',
|
|
},
|
|
},
|
|
},
|
|
'options': {
|
|
'tradesLimit': 1000,
|
|
'ordersLimit': 1000,
|
|
'watchPositions': {
|
|
'fetchPositionsSnapshot': True, # or False
|
|
'awaitPositionsSnapshot': True, # whether to wait for the positions snapshot before providing updates
|
|
},
|
|
},
|
|
'streaming': {
|
|
'keepAlive': 59000,
|
|
},
|
|
'exceptions': {
|
|
},
|
|
})
|
|
|
|
async def watch_public(self, suffix, messageHash, message):
|
|
url = self.urls['api']['ws']['swap'] + '/' + suffix
|
|
return await self.watch(url, messageHash, self.json(message), messageHash, message)
|
|
|
|
async def watch_public_multiple(self, suffix, messageHashes, message):
|
|
url = self.urls['api']['ws']['swap'] + '/' + suffix
|
|
return await self.watch_multiple(url, messageHashes, self.json(message), messageHashes, message)
|
|
|
|
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://testnet.aftermath.finance/docs/#/CCXT/service%3A%3Ahandlers%3A%3Accxt%3A%3Astream%3A%3Atrades
|
|
|
|
: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>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
topic = market['id'] + '@trade'
|
|
request: dict = {
|
|
'chId': market['id'],
|
|
}
|
|
message = self.extend(request, params)
|
|
trades = await self.watch_public('trades', topic, message)
|
|
if self.newUpdates:
|
|
limit = trades.getLimit(market['symbol'], limit)
|
|
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
|
|
|
|
def handle_trade(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "amount": 0.1,
|
|
# "cost": 0.1,
|
|
# "datetime": "string",
|
|
# "fee": null,
|
|
# "id": "string",
|
|
# "order": "string",
|
|
# "price": 0.1,
|
|
# "side": null,
|
|
# "symbol": "string",
|
|
# "takerOrMaker": null,
|
|
# "timestamp": null,
|
|
# "type": "string"
|
|
# }
|
|
#
|
|
trade = self.parse_trade(message)
|
|
symbol = trade['symbol']
|
|
market = self.market(symbol)
|
|
if not (symbol in self.trades):
|
|
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
stored = ArrayCache(limit)
|
|
self.trades[symbol] = stored
|
|
messageHash = market['id'] + '@trade'
|
|
trades = self.trades[symbol]
|
|
trades.append(trade)
|
|
self.trades[symbol] = trades
|
|
client.resolve(trades, messageHash)
|
|
|
|
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
|
|
https://testnet.aftermath.finance/docs/#/CCXT/service%3A%3Ahandlers%3A%3Accxt%3A%3Astream%3A%3Aorderbook
|
|
|
|
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
:param int [limit]: the maximum amount of order book entries to return.
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
topic = market['id'] + '@orderbook'
|
|
request: dict = {
|
|
'chId': market['id'],
|
|
}
|
|
message = self.extend(request, params)
|
|
orderbook = await self.watch_public('orderbook', topic, message)
|
|
return orderbook.limit()
|
|
|
|
def handle_order_book(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "asks": [
|
|
# []
|
|
# ],
|
|
# "bids": [
|
|
# []
|
|
# ],
|
|
# "datetime": "string",
|
|
# "nonce": 9007199254740991,
|
|
# "symbol": "string",
|
|
# "timestamp": 9007199254740991
|
|
# }
|
|
#
|
|
symbol = self.safe_string(message, 'symbol')
|
|
market = self.market(symbol)
|
|
topic = market['id'] + '@orderbook'
|
|
if not (symbol in self.orderbooks):
|
|
defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000)
|
|
subscription = client.subscriptions[topic]
|
|
limit = self.safe_integer(subscription, 'limit', defaultLimit)
|
|
self.orderbooks[symbol] = self.order_book({}, limit)
|
|
subscription['limit'] = limit
|
|
self.spawn(self.fetch_order_book_snapshot, client, message, subscription)
|
|
else:
|
|
orderbook = self.orderbooks[symbol]
|
|
prevNonce = self.safe_integer(orderbook, 'nonce')
|
|
nonce = self.safe_integer(message, 'nonce')
|
|
if nonce == (prevNonce + 1):
|
|
self.handle_order_book_message(client, message, orderbook)
|
|
client.resolve(orderbook, topic)
|
|
|
|
async def fetch_order_book_snapshot(self, client, message, subscription):
|
|
symbol = self.safe_string(message, 'symbol')
|
|
market = self.market(symbol)
|
|
messageHash = market['id'] + '@orderbook'
|
|
try:
|
|
defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000)
|
|
limit = self.safe_integer(subscription, 'limit', defaultLimit)
|
|
params = self.safe_dict(subscription, 'params')
|
|
snapshot = await self.fetch_rest_order_book_safe(symbol, limit, params)
|
|
if self.safe_value(self.orderbooks, symbol) is None:
|
|
# if the orderbook is dropped before the snapshot is received
|
|
return
|
|
orderbook = self.orderbooks[symbol]
|
|
orderbook.reset(snapshot)
|
|
self.orderbooks[symbol] = orderbook
|
|
client.resolve(orderbook, messageHash)
|
|
except Exception as e:
|
|
del client.subscriptions[messageHash]
|
|
client.reject(e, messageHash)
|
|
|
|
def handle_order_book_message(self, client: Client, message, orderbook):
|
|
self.handle_deltas(orderbook['asks'], self.safe_value(message, 'asks', []))
|
|
self.handle_deltas(orderbook['bids'], self.safe_value(message, 'bids', []))
|
|
timestamp = self.safe_integer(message, 'timestamp')
|
|
nonce = self.safe_integer(message, 'nonce')
|
|
orderbook['timestamp'] = timestamp
|
|
orderbook['datetime'] = self.iso8601(timestamp)
|
|
orderbook['nonce'] = nonce
|
|
return orderbook
|
|
|
|
def handle_delta(self, bookside, delta):
|
|
price = self.safe_float_2(delta, 'price', 0)
|
|
amount = self.safe_float_2(delta, 'quantity', 1)
|
|
bookside.store(price, amount)
|
|
|
|
def handle_deltas(self, bookside, deltas):
|
|
for i in range(0, len(deltas)):
|
|
self.handle_delta(bookside, deltas[i])
|
|
|
|
async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
|
|
"""
|
|
|
|
https://testnet.aftermath.finance/docs/#/CCXT/service%3A%3Ahandlers%3A%3Accxt%3A%3Astream%3A%3Apositions
|
|
|
|
watch all open positions
|
|
:param str[]|None symbols: list of unified market symbols
|
|
:param int [since]: the earliest time in ms to fetch positions for
|
|
:param int [limit]: the maximum number of position structures to retrieve
|
|
:param dict params: extra parameters specific to the exchange API endpoint
|
|
:param int [params.accountNumber]: account number to query orders for, required
|
|
:returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
messageHashes = []
|
|
symbols = self.market_symbols(symbols)
|
|
if not self.is_empty(symbols):
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
messageHashes.append('positions::' + symbol)
|
|
else:
|
|
messageHashes.append('positions')
|
|
accountNumber = self.safe_number(params, 'accountNumber')
|
|
request = {
|
|
'accountNumber': accountNumber,
|
|
}
|
|
message = self.extend(request, params)
|
|
suffix = 'positions'
|
|
url = self.urls['api']['ws']['swap'] + '/' + suffix
|
|
client = self.client(url)
|
|
self.set_positions_cache(client, symbols, params)
|
|
fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True)
|
|
awaitPositionsSnapshot = self.handle_option('watchPositions', 'awaitPositionsSnapshot', True)
|
|
if fetchPositionsSnapshot and awaitPositionsSnapshot and self.positions is None:
|
|
snapshot = await client.future('fetchPositionsSnapshot')
|
|
return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True)
|
|
newPositions = await self.watch_public_multiple(suffix, messageHashes, message)
|
|
if self.newUpdates:
|
|
return newPositions
|
|
return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True)
|
|
|
|
def set_positions_cache(self, client: Client, symbols: Strings = None, params: dict = {}):
|
|
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, symbols, params)
|
|
else:
|
|
self.positions = ArrayCacheBySymbolBySide()
|
|
|
|
async def load_positions_snapshot(self, client, messageHash, symbols, params):
|
|
positions = await self.fetch_positions(symbols, params)
|
|
self.positions = ArrayCacheBySymbolBySide()
|
|
cache = self.positions
|
|
for i in range(0, len(positions)):
|
|
position = positions[i]
|
|
cache.append(position)
|
|
# don't remove the future from the .futures cache
|
|
future = client.futures[messageHash]
|
|
future.resolve(cache)
|
|
client.resolve(cache, 'positions')
|
|
|
|
def handle_positions(self, client, message):
|
|
#
|
|
# {
|
|
# "collateral": 0.1,
|
|
# "contractSize": 0.1,
|
|
# "contracts": 0.1,
|
|
# "datetime": "string",
|
|
# "entryPrice": 0.1,
|
|
# "id": "0x895037c09dd1025a136b5a5789c4ea2481176adf4c3b0c3521d5d2039bdfc3ba:123",
|
|
# "initialMargin": 0.1,
|
|
# "initialMarginPercentage": 0.1,
|
|
# "leverage": 0.1,
|
|
# "liquidationPrice": 0.1,
|
|
# "maintenanceMargin": 0.1,
|
|
# "maintenanceMarginPercentage": 0.1,
|
|
# "marginMode": "cross",
|
|
# "marginRatio": 0.1,
|
|
# "notional": 0.1,
|
|
# "side": "long",
|
|
# "symbol": "BTC/USD:USDC",
|
|
# "timestamp": 9007199254740991,
|
|
# "unrealizedPnl": 0.1
|
|
# }
|
|
#
|
|
if self.positions is None:
|
|
self.positions = ArrayCacheBySymbolBySide()
|
|
cache = self.positions
|
|
symbol = self.safe_string(message, 'symbol')
|
|
market = self.safe_market(symbol)
|
|
position = self.parse_position(message, market)
|
|
cache.append(position)
|
|
messageHash = 'positions::' + market['symbol']
|
|
client.resolve(position, messageHash)
|
|
client.resolve([position], 'positions')
|
|
|
|
def handle_error_message(self, client: Client, message):
|
|
#
|
|
# User error: Expected Message::Text from client, got Ping(b\"\")
|
|
#
|
|
if isinstance(message, str):
|
|
if message.find('error') >= 0:
|
|
try:
|
|
feedback = self.id + ' ' + message
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
|
|
raise ExchangeError(message)
|
|
except Exception as error:
|
|
if isinstance(error, AuthenticationError):
|
|
messageHash = 'authenticated'
|
|
client.reject(error, messageHash)
|
|
if messageHash in client.subscriptions:
|
|
del client.subscriptions[messageHash]
|
|
else:
|
|
client.reject(error)
|
|
return True
|
|
return False
|
|
|
|
def handle_message(self, client: Client, message):
|
|
if self.handle_error_message(client, message):
|
|
return
|
|
# methods: Dict = {
|
|
# 'trade': self.handle_trade,
|
|
# }
|
|
if 'asks' in message:
|
|
self.handle_order_book(client, message)
|
|
elif 'notional' in message:
|
|
self.handle_positions(client, message)
|
|
else:
|
|
self.handle_trade(client, message)
|