UNPKG

sfccxt

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges

550 lines (533 loc) 24.5 kB
'use strict'; // --------------------------------------------------------------------------- const ndaxRest = require ('../ndax.js'); const { ArrayCache } = require ('./base/Cache'); // --------------------------------------------------------------------------- module.exports = class ndax extends ndaxRest { describe () { return this.deepExtend (super.describe (), { 'has': { 'ws': true, 'watchOrderBook': true, 'watchTrades': true, '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, // }, }); } requestId () { const requestId = this.sum (this.safeInteger (this.options, 'requestId', 0), 1); this.options['requestId'] = requestId; return requestId; } async watchTicker (symbol, params = {}) { /** * @method * @name ndax#watchTicker * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} params extra parameters specific to the ndax api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure} */ const omsId = this.safeInteger (this.options, 'omsId', 1); await this.loadMarkets (); const market = this.market (symbol); const name = 'SubscribeLevel1'; const messageHash = name + ':' + market['id']; const url = this.urls['api']['ws']; const requestId = this.requestId (); const payload = { 'OMSId': omsId, 'InstrumentId': parseInt (market['id']), // conditionally optional // 'Symbol': market['info']['symbol'], // conditionally optional }; const request = { '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': this.json (payload), // JSON-formatted string containing the data being sent with the message }; const message = this.extend (request, params); return await this.watch (url, messageHash, message, messageHash); } handleTicker (client, message) { const payload = this.safeValue (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" // } // const ticker = this.parseTicker (payload); const symbol = ticker['symbol']; const market = this.market (symbol); this.tickers[symbol] = ticker; const name = 'SubscribeLevel1'; const messageHash = name + ':' + market['id']; client.resolve (ticker, messageHash); } async watchTrades (symbol, since = undefined, limit = undefined, params = {}) { /** * @method * @name ndax#watchTrades * @description get the list of most recent trades for a particular symbol * @param {string} symbol unified symbol of the market to fetch trades for * @param {int|undefined} since timestamp in ms of the earliest trade to fetch * @param {int|undefined} limit the maximum amount of trades to fetch * @param {object} params extra parameters specific to the ndax api endpoint * @returns {[object]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html?#public-trades} */ const omsId = this.safeInteger (this.options, 'omsId', 1); await this.loadMarkets (); const market = this.market (symbol); symbol = market['symbol']; const name = 'SubscribeTrades'; const messageHash = name + ':' + market['id']; const url = this.urls['api']['ws']; const requestId = this.requestId (); const payload = { 'OMSId': omsId, 'InstrumentId': parseInt (market['id']), // conditionally optional 'IncludeLastCount': 100, // the number of previous trades to retrieve in the immediate snapshot, 100 by default }; const request = { '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': this.json (payload), // JSON-formatted string containing the data being sent with the message }; const message = this.extend (request, params); const trades = await this.watch (url, messageHash, message, messageHash); if (this.newUpdates) { limit = trades.getLimit (symbol, limit); } return this.filterBySinceLimit (trades, since, limit, 'timestamp', true); } handleTrades (client, message) { const payload = this.safeValue (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 // ] // ] // const name = 'SubscribeTrades'; const updates = {}; for (let i = 0; i < payload.length; i++) { const trade = this.parseTrade (payload[i]); const symbol = trade['symbol']; let tradesArray = this.safeValue (this.trades, symbol); if (tradesArray === undefined) { const limit = this.safeInteger (this.options, 'tradesLimit', 1000); tradesArray = new ArrayCache (limit); } tradesArray.append (trade); this.trades[symbol] = tradesArray; updates[symbol] = true; } const symbols = Object.keys (updates); for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; const market = this.market (symbol); const messageHash = name + ':' + market['id']; const tradesArray = this.safeValue (this.trades, symbol); client.resolve (tradesArray, messageHash); } } async watchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name ndax#watchOHLCV * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @param {string} symbol unified symbol of the market to fetch OHLCV data for * @param {string} timeframe the length of time each candle represents * @param {int|undefined} since timestamp in ms of the earliest candle to fetch * @param {int|undefined} limit the maximum amount of candles to fetch * @param {object} params extra parameters specific to the ndax api endpoint * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume */ const omsId = this.safeInteger (this.options, 'omsId', 1); await this.loadMarkets (); const market = this.market (symbol); symbol = market['symbol']; const name = 'SubscribeTicker'; const messageHash = name + ':' + timeframe + ':' + market['id']; const url = this.urls['api']['ws']; const requestId = this.requestId (); const payload = { 'OMSId': omsId, 'InstrumentId': parseInt (market['id']), // conditionally optional 'Interval': parseInt (this.timeframes[timeframe]), 'IncludeLastCount': 100, // the number of previous candles to retrieve in the immediate snapshot, 100 by default }; const request = { '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': this.json (payload), // JSON-formatted string containing the data being sent with the message }; const message = this.extend (request, params); const ohlcv = await this.watch (url, messageHash, message, messageHash); if (this.newUpdates) { limit = ohlcv.getLimit (symbol, limit); } return this.filterBySinceLimit (ohlcv, since, limit, 0, true); } handleOHLCV (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]], // } // const payload = this.safeValue (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 // ] // ] // const updates = {}; for (let i = 0; i < payload.length; i++) { const ohlcv = payload[i]; const marketId = this.safeString (ohlcv, 8); const market = this.safeMarket (marketId); const symbol = market['symbol']; updates[marketId] = {}; this.ohlcvs[symbol] = this.safeValue (this.ohlcvs, symbol, {}); const keys = Object.keys (this.timeframes); for (let j = 0; j < keys.length; j++) { const timeframe = keys[j]; const interval = this.timeframes[timeframe]; const duration = parseInt (interval) * 1000; const timestamp = this.safeInteger (ohlcv, 0); const parsed = [ parseInt (timestamp / duration) * duration, this.safeFloat (ohlcv, 3), this.safeFloat (ohlcv, 1), this.safeFloat (ohlcv, 2), this.safeFloat (ohlcv, 4), this.safeFloat (ohlcv, 5), ]; const stored = this.safeValue (this.ohlcvs[symbol], timeframe, []); const length = stored.length; if (length && (parsed[0] === stored[length - 1][0])) { const previous = stored[length - 1]; stored[length - 1] = [ parsed[0], previous[1], Math.max (parsed[1], previous[1]), Math.min (parsed[2], previous[2]), parsed[4], this.sum (parsed[5], previous[5]), ]; updates[marketId][timeframe] = true; } else { if (length && (parsed[0] < stored[length - 1][0])) { continue; } else { stored.push (parsed); const limit = this.safeInteger (this.options, 'OHLCVLimit', 1000); if (length >= limit) { stored.shift (); } updates[marketId][timeframe] = true; } } this.ohlcvs[symbol][timeframe] = stored; } } const name = 'SubscribeTicker'; const marketIds = Object.keys (updates); for (let i = 0; i < marketIds.length; i++) { const marketId = marketIds[i]; const timeframes = Object.keys (updates[marketId]); for (let j = 0; j < timeframes.length; j++) { const timeframe = timeframes[j]; const messageHash = name + ':' + timeframe + ':' + marketId; const market = this.safeMarket (marketId); const symbol = market['symbol']; const stored = this.safeValue (this.ohlcvs[symbol], timeframe, []); client.resolve (stored, messageHash); } } } async watchOrderBook (symbol, limit = undefined, params = {}) { /** * @method * @name ndax#watchOrderBook * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int|undefined} limit the maximum amount of order book entries to return * @param {object} params extra parameters specific to the ndax api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure} indexed by market symbols */ const omsId = this.safeInteger (this.options, 'omsId', 1); await this.loadMarkets (); const market = this.market (symbol); symbol = market['symbol']; const name = 'SubscribeLevel2'; const messageHash = name + ':' + market['id']; const url = this.urls['api']['ws']; const requestId = this.requestId (); limit = (limit === undefined) ? 100 : limit; const payload = { 'OMSId': omsId, 'InstrumentId': parseInt (market['id']), // conditionally optional // 'Symbol': market['info']['symbol'], // conditionally optional 'Depth': limit, // default 100 }; const request = { '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': this.json (payload), // JSON-formatted string containing the data being sent with the message }; const subscription = { 'id': requestId, 'messageHash': messageHash, 'name': name, 'symbol': symbol, 'marketId': market['id'], 'method': this.handleOrderBookSubscription, 'limit': limit, 'params': params, }; const message = this.extend (request, params); const orderbook = await this.watch (url, messageHash, message, messageHash, subscription); return orderbook.limit (); } handleOrderBook (client, message) { // // { // m: 3, // i: 2, // n: 'Level2UpdateEvent', // o: [[2,1,1608208308265,0,20782.49,1,25000,8,1,1]] // } // const payload = this.safeValue (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 // ], // const firstBidAsk = this.safeValue (payload, 0, []); const marketId = this.safeString (firstBidAsk, 7); if (marketId === undefined) { return message; } const market = this.safeMarket (marketId); const symbol = market['symbol']; const orderbook = this.safeValue (this.orderbooks, symbol); if (orderbook === undefined) { return message; } let timestamp = undefined; let nonce = undefined; for (let i = 0; i < payload.length; i++) { const bidask = payload[i]; if (timestamp === undefined) { timestamp = this.safeInteger (bidask, 2); } else { const newTimestamp = this.safeInteger (bidask, 2); timestamp = Math.max (timestamp, newTimestamp); } if (nonce === undefined) { nonce = this.safeInteger (bidask, 0); } else { const newNonce = this.safeInteger (bidask, 0); nonce = Math.max (nonce, newNonce); } // 0 new, 1 update, 2 remove const type = this.safeInteger (bidask, 3); const price = this.safeFloat (bidask, 6); const amount = this.safeFloat (bidask, 8); const side = this.safeInteger (bidask, 9); // 0 buy, 1 sell, 2 short reserved for future use, 3 unknown const orderbookSide = (side === 0) ? orderbook['bids'] : orderbook['asks']; // 0 new, 1 update, 2 remove if (type === 0) { orderbookSide.store (price, amount); } else if (type === 1) { orderbookSide.store (price, amount); } else if (type === 2) { orderbookSide.store (price, 0); } } orderbook['nonce'] = nonce; orderbook['timestamp'] = timestamp; orderbook['datetime'] = this.iso8601 (timestamp); const name = 'SubscribeLevel2'; const messageHash = name + ':' + marketId; this.orderbooks[symbol] = orderbook; client.resolve (orderbook, messageHash); } handleOrderBookSubscription (client, message, subscription) { // // { // m: 1, // i: 1, // n: 'SubscribeLevel2', // o: [[1,1,1608204295901,0,20782.49,1,18200,8,1,0]] // } // const payload = this.safeValue (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 // ], // ] // const symbol = this.safeString (subscription, 'symbol'); const snapshot = this.parseOrderBook (payload, symbol); const limit = this.safeInteger (subscription, 'limit'); const orderbook = this.orderBook (snapshot, limit); this.orderbooks[symbol] = orderbook; const messageHash = this.safeString (subscription, 'messageHash'); client.resolve (orderbook, messageHash); } handleSubscriptionStatus (client, message) { // // { // m: 1, // i: 1, // n: 'SubscribeLevel2', // o: '[[1,1,1608204295901,0,20782.49,1,18200,8,1,0]]' // } // const subscriptionsById = this.indexBy (client.subscriptions, 'id'); const id = this.safeInteger (message, 'i'); const subscription = this.safeValue (subscriptionsById, id); if (subscription !== undefined) { const method = this.safeValue (subscription, 'method'); if (method === undefined) { return message; } else { return method.call (this, client, message, subscription); } } } handleMessage (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]]' // } // const payload = this.safeString (message, 'o'); if (payload === undefined) { return message; } message['o'] = JSON.parse (payload); const methods = { 'SubscribeLevel2': this.handleSubscriptionStatus, 'SubscribeLevel1': this.handleTicker, 'Level2UpdateEvent': this.handleOrderBook, 'Level1UpdateEvent': this.handleTicker, 'SubscribeTrades': this.handleTrades, 'TradeDataUpdateEvent': this.handleTrades, 'SubscribeTicker': this.handleOHLCV, 'TickerDataUpdateEvent': this.handleOHLCV, }; const event = this.safeString (message, 'n'); const method = this.safeValue (methods, event); if (method === undefined) { return message; } else { return method.call (this, client, message); } } };