520 lines
22 KiB
Python
520 lines
22 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
|
|
import json
|
|
from ccxt.base.types import Any, Int, OrderBook, Ticker, Trade
|
|
from ccxt.async_support.base.ws.client import Client
|
|
from typing import List
|
|
|
|
|
|
class ndax(ccxt.async_support.ndax):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(ndax, self).describe(), {
|
|
'has': {
|
|
'ws': True,
|
|
'watchOrderBook': True,
|
|
'watchTrades': True,
|
|
'watchTradesForSymbols': False,
|
|
'watchTicker': True,
|
|
'watchOHLCV': True,
|
|
},
|
|
'urls': {
|
|
'test': {
|
|
'ws': 'wss://ndaxmarginstaging.cdnhop.net:10456/WSAdminGatewa/',
|
|
},
|
|
'api': {
|
|
'ws': 'wss://api.ndax.io/WSGateway',
|
|
},
|
|
},
|
|
# 'options': {
|
|
# 'tradesLimit': 1000,
|
|
# 'ordersLimit': 1000,
|
|
# 'OHLCVLimit': 1000,
|
|
# },
|
|
})
|
|
|
|
def request_id(self):
|
|
requestId = self.sum(self.safe_integer(self.options, 'requestId', 0), 1)
|
|
self.options['requestId'] = requestId
|
|
return requestId
|
|
|
|
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://apidoc.ndax.io/#subscribelevel1
|
|
|
|
: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>`
|
|
"""
|
|
omsId = self.safe_integer(self.options, 'omsId', 1)
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
name = 'SubscribeLevel1'
|
|
messageHash = name + ':' + market['id']
|
|
url = self.urls['api']['ws']
|
|
requestId = self.request_id()
|
|
payload: dict = {
|
|
'OMSId': omsId,
|
|
'InstrumentId': int(market['id']), # conditionally optional
|
|
# 'Symbol': market['info']['symbol'], # conditionally optional
|
|
}
|
|
request: dict = {
|
|
'm': 0, # message type, 0 request, 1 reply, 2 subscribe, 3 event, unsubscribe, 5 error
|
|
'i': requestId, # sequence number identifies an individual request or request-and-response pair, to your application
|
|
'n': name, # function name is the name of the function being called or that the server is responding to, the server echoes your call
|
|
'o': self.json(payload), # JSON-formatted string containing the data being sent with the message
|
|
}
|
|
message = self.extend(request, params)
|
|
return await self.watch(url, messageHash, message, messageHash)
|
|
|
|
def handle_ticker(self, client: Client, message):
|
|
payload = self.safe_value(message, 'o', {})
|
|
#
|
|
# {
|
|
# "OMSId": 1,
|
|
# "InstrumentId": 1,
|
|
# "BestBid": 6423.57,
|
|
# "BestOffer": 6436.53,
|
|
# "LastTradedPx": 6423.57,
|
|
# "LastTradedQty": 0.96183964,
|
|
# "LastTradeTime": 1534862990343,
|
|
# "SessionOpen": 6249.64,
|
|
# "SessionHigh": 11111,
|
|
# "SessionLow": 4433,
|
|
# "SessionClose": 6249.64,
|
|
# "Volume": 0.96183964,
|
|
# "CurrentDayVolume": 3516.31668185,
|
|
# "CurrentDayNumTrades": 8529,
|
|
# "CurrentDayPxChange": 173.93,
|
|
# "CurrentNotional": 0.0,
|
|
# "Rolling24HrNotional": 0.0,
|
|
# "Rolling24HrVolume": 4319.63870783,
|
|
# "Rolling24NumTrades": 10585,
|
|
# "Rolling24HrPxChange": -0.4165607307408487,
|
|
# "TimeStamp": "1534862990358"
|
|
# }
|
|
#
|
|
ticker = self.parse_ticker(payload)
|
|
symbol = ticker['symbol']
|
|
market = self.market(symbol)
|
|
self.tickers[symbol] = ticker
|
|
name = 'SubscribeLevel1'
|
|
messageHash = name + ':' + market['id']
|
|
client.resolve(ticker, messageHash)
|
|
|
|
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
get the list of most recent trades for a particular symbol
|
|
|
|
https://apidoc.ndax.io/#subscribetrades
|
|
|
|
:param str symbol: 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>`
|
|
"""
|
|
omsId = self.safe_integer(self.options, 'omsId', 1)
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
name = 'SubscribeTrades'
|
|
messageHash = name + ':' + market['id']
|
|
url = self.urls['api']['ws']
|
|
requestId = self.request_id()
|
|
payload: dict = {
|
|
'OMSId': omsId,
|
|
'InstrumentId': int(market['id']), # conditionally optional
|
|
'IncludeLastCount': 100, # the number of previous trades to retrieve in the immediate snapshot, 100 by default
|
|
}
|
|
request: dict = {
|
|
'm': 0, # message type, 0 request, 1 reply, 2 subscribe, 3 event, unsubscribe, 5 error
|
|
'i': requestId, # sequence number identifies an individual request or request-and-response pair, to your application
|
|
'n': name, # function name is the name of the function being called or that the server is responding to, the server echoes your call
|
|
'o': self.json(payload), # JSON-formatted string containing the data being sent with the message
|
|
}
|
|
message = self.extend(request, params)
|
|
trades = await self.watch(url, messageHash, message, messageHash)
|
|
if self.newUpdates:
|
|
limit = trades.getLimit(symbol, limit)
|
|
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
|
|
|
|
def handle_trades(self, client: Client, message):
|
|
payload = self.safe_value(message, 'o', [])
|
|
#
|
|
# initial snapshot
|
|
#
|
|
# [
|
|
# [
|
|
# 6913253, # 0 TradeId
|
|
# 8, # 1 ProductPairCode
|
|
# 0.03340802, # 2 Quantity
|
|
# 19116.08, # 3 Price
|
|
# 2543425077, # 4 Order1
|
|
# 2543425482, # 5 Order2
|
|
# 1606935922416, # 6 Tradetime
|
|
# 0, # 7 Direction
|
|
# 1, # 8 TakerSide
|
|
# 0, # 9 BlockTrade
|
|
# 0, # 10 Either Order1ClientId or Order2ClientId
|
|
# ]
|
|
# ]
|
|
#
|
|
name = 'SubscribeTrades'
|
|
updates: dict = {}
|
|
for i in range(0, len(payload)):
|
|
trade = self.parse_trade(payload[i])
|
|
symbol = trade['symbol']
|
|
tradesArray = self.safe_value(self.trades, symbol)
|
|
if tradesArray is None:
|
|
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
tradesArray = ArrayCache(limit)
|
|
tradesArray.append(trade)
|
|
self.trades[symbol] = tradesArray
|
|
updates[symbol] = True
|
|
symbols = list(updates.keys())
|
|
for i in range(0, len(symbols)):
|
|
symbol = symbols[i]
|
|
market = self.market(symbol)
|
|
messageHash = name + ':' + market['id']
|
|
tradesArray = self.safe_value(self.trades, symbol)
|
|
client.resolve(tradesArray, messageHash)
|
|
|
|
async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
"""
|
|
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://apidoc.ndax.io/#subscribeticker
|
|
|
|
: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
|
|
"""
|
|
omsId = self.safe_integer(self.options, 'omsId', 1)
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
name = 'SubscribeTicker'
|
|
messageHash = name + ':' + timeframe + ':' + market['id']
|
|
url = self.urls['api']['ws']
|
|
requestId = self.request_id()
|
|
payload: dict = {
|
|
'OMSId': omsId,
|
|
'InstrumentId': int(market['id']), # conditionally optional
|
|
'Interval': int(self.safe_string(self.timeframes, timeframe, timeframe)),
|
|
'IncludeLastCount': 100, # the number of previous candles to retrieve in the immediate snapshot, 100 by default
|
|
}
|
|
request: dict = {
|
|
'm': 0, # message type, 0 request, 1 reply, 2 subscribe, 3 event, unsubscribe, 5 error
|
|
'i': requestId, # sequence number identifies an individual request or request-and-response pair, to your application
|
|
'n': name, # function name is the name of the function being called or that the server is responding to, the server echoes your call
|
|
'o': self.json(payload), # JSON-formatted string containing the data being sent with the message
|
|
}
|
|
message = self.extend(request, params)
|
|
ohlcv = await self.watch(url, messageHash, message, messageHash)
|
|
if self.newUpdates:
|
|
limit = ohlcv.getLimit(symbol, limit)
|
|
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
|
|
|
|
def handle_ohlcv(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "m": 1,
|
|
# "i": 1,
|
|
# "n": "SubscribeTicker",
|
|
# "o": [[1608284160000,23113.52,23070.88,23075.76,23075.39,162.44964300,23075.38,23075.39,8,1608284100000]],
|
|
# }
|
|
#
|
|
payload = self.safe_value(message, 'o', [])
|
|
#
|
|
# [
|
|
# [
|
|
# 1501603632000, # 0 DateTime
|
|
# 2700.33, # 1 High
|
|
# 2687.01, # 2 Low
|
|
# 2687.01, # 3 Open
|
|
# 2687.01, # 4 Close
|
|
# 24.86100992, # 5 Volume
|
|
# 0, # 6 Inside Bid Price
|
|
# 2870.95, # 7 Inside Ask Price
|
|
# 1 # 8 InstrumentId
|
|
# 1608290188062.7678, # 9 candle timestamp
|
|
# ]
|
|
# ]
|
|
#
|
|
updates: dict = {}
|
|
for i in range(0, len(payload)):
|
|
ohlcv = payload[i]
|
|
marketId = self.safe_string(ohlcv, 8)
|
|
market = self.safe_market(marketId)
|
|
symbol = market['symbol']
|
|
updates[marketId] = {}
|
|
self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
|
|
keys = list(self.timeframes.keys())
|
|
for j in range(0, len(keys)):
|
|
timeframe = keys[j]
|
|
interval = self.safe_string(self.timeframes, timeframe, timeframe)
|
|
duration = int(interval) * 1000
|
|
timestamp = self.safe_integer(ohlcv, 0)
|
|
parsed = [
|
|
self.parse_to_int((timestamp / duration) * duration),
|
|
self.safe_float(ohlcv, 3),
|
|
self.safe_float(ohlcv, 1),
|
|
self.safe_float(ohlcv, 2),
|
|
self.safe_float(ohlcv, 4),
|
|
self.safe_float(ohlcv, 5),
|
|
]
|
|
stored = self.safe_value(self.ohlcvs[symbol], timeframe, [])
|
|
length = len(stored)
|
|
if length and (parsed[0] == stored[length - 1][0]):
|
|
previous = stored[length - 1]
|
|
stored[length - 1] = [
|
|
parsed[0],
|
|
previous[1],
|
|
max(parsed[1], previous[1]),
|
|
min(parsed[2], previous[2]),
|
|
parsed[4],
|
|
self.sum(parsed[5], previous[5]),
|
|
]
|
|
updates[marketId][timeframe] = True
|
|
else:
|
|
if length and (parsed[0] < stored[length - 1][0]):
|
|
continue
|
|
else:
|
|
stored.append(parsed)
|
|
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
|
|
if length >= limit:
|
|
stored.pop(0)
|
|
updates[marketId][timeframe] = True
|
|
self.ohlcvs[symbol][timeframe] = stored
|
|
name = 'SubscribeTicker'
|
|
marketIds = list(updates.keys())
|
|
for i in range(0, len(marketIds)):
|
|
marketId = marketIds[i]
|
|
timeframes = list(updates[marketId].keys())
|
|
for j in range(0, len(timeframes)):
|
|
timeframe = timeframes[j]
|
|
messageHash = name + ':' + timeframe + ':' + marketId
|
|
market = self.safe_market(marketId)
|
|
symbol = market['symbol']
|
|
stored = self.safe_value(self.ohlcvs[symbol], timeframe, [])
|
|
client.resolve(stored, messageHash)
|
|
|
|
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://apidoc.ndax.io/#subscribelevel2
|
|
|
|
: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
|
|
"""
|
|
omsId = self.safe_integer(self.options, 'omsId', 1)
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
symbol = market['symbol']
|
|
name = 'SubscribeLevel2'
|
|
messageHash = name + ':' + market['id']
|
|
url = self.urls['api']['ws']
|
|
requestId = self.request_id()
|
|
limit = 100 if (limit is None) else limit
|
|
payload: dict = {
|
|
'OMSId': omsId,
|
|
'InstrumentId': int(market['id']), # conditionally optional
|
|
# 'Symbol': market['info']['symbol'], # conditionally optional
|
|
'Depth': limit, # default 100
|
|
}
|
|
request: dict = {
|
|
'm': 0, # message type, 0 request, 1 reply, 2 subscribe, 3 event, unsubscribe, 5 error
|
|
'i': requestId, # sequence number identifies an individual request or request-and-response pair, to your application
|
|
'n': name, # function name is the name of the function being called or that the server is responding to, the server echoes your call
|
|
'o': self.json(payload), # JSON-formatted string containing the data being sent with the message
|
|
}
|
|
subscription: dict = {
|
|
'id': requestId,
|
|
'messageHash': messageHash,
|
|
'name': name,
|
|
'symbol': symbol,
|
|
'marketId': market['id'],
|
|
'method': self.handle_order_book_subscription,
|
|
'limit': limit,
|
|
'params': params,
|
|
}
|
|
message = self.extend(request, params)
|
|
orderbook = await self.watch(url, messageHash, message, messageHash, subscription)
|
|
return orderbook.limit()
|
|
|
|
def handle_order_book(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "m": 3,
|
|
# "i": 2,
|
|
# "n": "Level2UpdateEvent",
|
|
# "o": [[2,1,1608208308265,0,20782.49,1,25000,8,1,1]]
|
|
# }
|
|
#
|
|
payload = self.safe_value(message, 'o', [])
|
|
#
|
|
# [
|
|
# 0, # 0 MDUpdateId
|
|
# 1, # 1 Number of Unique Accounts
|
|
# 123, # 2 ActionDateTime in Posix format X 1000
|
|
# 0, # 3 ActionType 0(New), 1(Update), 2(Delete)
|
|
# 0.0, # 4 LastTradePrice
|
|
# 0, # 5 Number of Orders
|
|
# 0.0, # 6 Price
|
|
# 0, # 7 ProductPairCode
|
|
# 0.0, # 8 Quantity
|
|
# 0, # 9 Side
|
|
# ],
|
|
#
|
|
firstBidAsk = self.safe_value(payload, 0, [])
|
|
marketId = self.safe_string(firstBidAsk, 7)
|
|
if marketId is None:
|
|
return
|
|
market = self.safe_market(marketId)
|
|
symbol = market['symbol']
|
|
orderbook = self.safe_value(self.orderbooks, symbol)
|
|
if orderbook is None:
|
|
return
|
|
timestamp = None
|
|
nonce = None
|
|
for i in range(0, len(payload)):
|
|
bidask = payload[i]
|
|
if timestamp is None:
|
|
timestamp = self.safe_integer(bidask, 2)
|
|
else:
|
|
newTimestamp = self.safe_integer(bidask, 2)
|
|
timestamp = max(timestamp, newTimestamp)
|
|
if nonce is None:
|
|
nonce = self.safe_integer(bidask, 0)
|
|
else:
|
|
newNonce = self.safe_integer(bidask, 0)
|
|
nonce = max(nonce, newNonce)
|
|
# 0 new, 1 update, 2 remove
|
|
type = self.safe_integer(bidask, 3)
|
|
price = self.safe_float(bidask, 6)
|
|
amount = self.safe_float(bidask, 8)
|
|
side = self.safe_integer(bidask, 9)
|
|
# 0 buy, 1 sell, 2 short reserved for future use, 3 unknown
|
|
orderbookSide = orderbook['bids'] if (side == 0) else orderbook['asks']
|
|
# 0 new, 1 update, 2 remove
|
|
if type == 0:
|
|
orderbookSide.store(price, amount)
|
|
elif type == 1:
|
|
orderbookSide.store(price, amount)
|
|
elif type == 2:
|
|
orderbookSide.store(price, 0)
|
|
orderbook['nonce'] = nonce
|
|
orderbook['timestamp'] = timestamp
|
|
orderbook['datetime'] = self.iso8601(timestamp)
|
|
name = 'SubscribeLevel2'
|
|
messageHash = name + ':' + marketId
|
|
self.orderbooks[symbol] = orderbook
|
|
client.resolve(orderbook, messageHash)
|
|
|
|
def handle_order_book_subscription(self, client: Client, message, subscription):
|
|
#
|
|
# {
|
|
# "m": 1,
|
|
# "i": 1,
|
|
# "n": "SubscribeLevel2",
|
|
# "o": [[1,1,1608204295901,0,20782.49,1,18200,8,1,0]]
|
|
# }
|
|
#
|
|
payload = self.safe_value(message, 'o', [])
|
|
#
|
|
# [
|
|
# [
|
|
# 0, # 0 MDUpdateId
|
|
# 1, # 1 Number of Unique Accounts
|
|
# 123, # 2 ActionDateTime in Posix format X 1000
|
|
# 0, # 3 ActionType 0(New), 1(Update), 2(Delete)
|
|
# 0.0, # 4 LastTradePrice
|
|
# 0, # 5 Number of Orders
|
|
# 0.0, # 6 Price
|
|
# 0, # 7 ProductPairCode
|
|
# 0.0, # 8 Quantity
|
|
# 0, # 9 Side
|
|
# ],
|
|
# ]
|
|
#
|
|
symbol = self.safe_string(subscription, 'symbol')
|
|
snapshot = self.parse_order_book(payload, symbol)
|
|
limit = self.safe_integer(subscription, 'limit')
|
|
orderbook = self.order_book(snapshot, limit)
|
|
self.orderbooks[symbol] = orderbook
|
|
messageHash = self.safe_string(subscription, 'messageHash')
|
|
client.resolve(orderbook, messageHash)
|
|
|
|
def handle_subscription_status(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "m": 1,
|
|
# "i": 1,
|
|
# "n": "SubscribeLevel2",
|
|
# "o": "[[1,1,1608204295901,0,20782.49,1,18200,8,1,0]]"
|
|
# }
|
|
#
|
|
subscriptionsById = self.index_by(client.subscriptions, 'id')
|
|
id = self.safe_integer(message, 'i')
|
|
subscription = self.safe_value(subscriptionsById, id)
|
|
if subscription is not None:
|
|
method = self.safe_value(subscription, 'method')
|
|
if method is not None:
|
|
method(client, message, subscription)
|
|
|
|
def handle_message(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "m": 0, # message type, 0 request, 1 reply, 2 subscribe, 3 event, unsubscribe, 5 error
|
|
# "i": 0, # sequence number identifies an individual request or request-and-response pair, to your application
|
|
# "n":"function name", # function name is the name of the function being called or that the server is responding to, the server echoes your call
|
|
# "o":"payload", # JSON-formatted string containing the data being sent with the message
|
|
# }
|
|
#
|
|
# {
|
|
# "m": 1,
|
|
# "i": 1,
|
|
# "n": "SubscribeLevel2",
|
|
# "o": "[[1,1,1608204295901,0,20782.49,1,18200,8,1,0]]"
|
|
# }
|
|
#
|
|
# {
|
|
# "m": 3,
|
|
# "i": 2,
|
|
# "n": "Level2UpdateEvent",
|
|
# "o": "[[2,1,1608208308265,0,20782.49,1,25000,8,1,1]]"
|
|
# }
|
|
#
|
|
payload = self.safe_string(message, 'o')
|
|
if payload is None:
|
|
return
|
|
message['o'] = json.loads(payload)
|
|
methods: dict = {
|
|
'SubscribeLevel2': self.handle_subscription_status,
|
|
'SubscribeLevel1': self.handle_ticker,
|
|
'Level2UpdateEvent': self.handle_order_book,
|
|
'Level1UpdateEvent': self.handle_ticker,
|
|
'SubscribeTrades': self.handle_trades,
|
|
'TradeDataUpdateEvent': self.handle_trades,
|
|
'SubscribeTicker': self.handle_ohlcv,
|
|
'TickerDataUpdateEvent': self.handle_ohlcv,
|
|
}
|
|
event = self.safe_string(message, 'n')
|
|
method = self.safe_value(methods, event)
|
|
if method is not None:
|
|
method(client, message)
|