UNPKG

sfccxt

Version:

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

1,142 lines (1,110 loc) 48.1 kB
'use strict'; // --------------------------------------------------------------------------- const krakenRest = require ('../kraken.js'); const { BadSymbol, BadRequest, ExchangeError, NotSupported, InvalidNonce } = require ('../base/errors'); const { ArrayCache, ArrayCacheByTimestamp, ArrayCacheBySymbolById } = require ('./base/Cache'); // --------------------------------------------------------------------------- module.exports = class kraken extends krakenRest { describe () { return this.deepExtend (super.describe (), { 'has': { 'ws': true, 'watchBalance': false, // no such type of subscription as of 2021-01-05 'watchMyTrades': true, 'watchOHLCV': true, 'watchOrderBook': true, 'watchOrders': true, 'watchTicker': true, 'watchTickers': false, // for now 'watchTrades': true, // 'watchHeartbeat': true, // 'watchStatus': true, }, 'urls': { 'api': { 'ws': { 'public': 'wss://ws.kraken.com', 'private': 'wss://ws-auth.kraken.com', 'beta': 'wss://beta-ws.kraken.com', }, }, }, 'versions': { 'ws': '0.2.0', }, 'options': { 'tradesLimit': 1000, 'OHLCVLimit': 1000, 'ordersLimit': 1000, 'symbolsByOrderId': {}, 'checksum': true, }, 'exceptions': { 'ws': { 'exact': { 'Event(s) not found': BadRequest, }, 'broad': { 'Currency pair not in ISO 4217-A3 format': BadSymbol, }, }, }, }); } handleTicker (client, message, subscription) { // // [ // 0, // channelID // { // "a": [ "5525.40000", 1, "1.000" ], // ask, wholeAskVolume, askVolume // "b": [ "5525.10000", 1, "1.000" ], // bid, wholeBidVolume, bidVolume // "c": [ "5525.10000", "0.00398963" ], // closing price, volume // "h": [ "5783.00000", "5783.00000" ], // high price today, high price 24h ago // "l": [ "5505.00000", "5505.00000" ], // low price today, low price 24h ago // "o": [ "5760.70000", "5763.40000" ], // open price today, open price 24h ago // "p": [ "5631.44067", "5653.78939" ], // vwap today, vwap 24h ago // "t": [ 11493, 16267 ], // number of trades today, 24 hours ago // "v": [ "2634.11501494", "3591.17907851" ], // volume today, volume 24 hours ago // }, // "ticker", // "XBT/USD" // ] // const wsName = message[3]; const name = 'ticker'; const messageHash = name + ':' + wsName; const market = this.safeValue (this.options['marketsByWsName'], wsName); const symbol = market['symbol']; const ticker = message[1]; const vwap = this.safeFloat (ticker['p'], 0); let quoteVolume = undefined; const baseVolume = this.safeFloat (ticker['v'], 0); if (baseVolume !== undefined && vwap !== undefined) { quoteVolume = baseVolume * vwap; } const last = this.safeFloat (ticker['c'], 0); const timestamp = this.milliseconds (); const result = { 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': this.safeFloat (ticker['h'], 0), 'low': this.safeFloat (ticker['l'], 0), 'bid': this.safeFloat (ticker['b'], 0), 'bidVolume': this.safeFloat (ticker['b'], 2), 'ask': this.safeFloat (ticker['a'], 0), 'askVolume': this.safeFloat (ticker['a'], 2), 'vwap': vwap, 'open': this.safeFloat (ticker['o'], 0), 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'info': ticker, }; // todo add support for multiple tickers (may be tricky) // kraken confirms multi-pair subscriptions separately one by one // trigger correct watchTickers calls upon receiving any of symbols this.tickers[symbol] = result; client.resolve (result, messageHash); } handleTrades (client, message, subscription) { // // [ // 0, // channelID // [ // price volume time side type misc // [ "5541.20000", "0.15850568", "1534614057.321597", "s", "l", "" ], // [ "6060.00000", "0.02455000", "1534614057.324998", "b", "l", "" ], // ], // "trade", // "XBT/USD" // ] // const wsName = this.safeString (message, 3); const name = this.safeString (message, 2); const messageHash = name + ':' + wsName; const market = this.safeValue (this.options['marketsByWsName'], wsName); const symbol = market['symbol']; let stored = this.safeValue (this.trades, symbol); if (stored === undefined) { const limit = this.safeInteger (this.options, 'tradesLimit', 1000); stored = new ArrayCache (limit); this.trades[symbol] = stored; } const trades = this.safeValue (message, 1, []); const parsed = this.parseTrades (trades, market); for (let i = 0; i < parsed.length; i++) { stored.append (parsed[i]); } client.resolve (stored, messageHash); } handleOHLCV (client, message, subscription) { // // [ // 216, // channelID // [ // '1574454214.962096', // Time, seconds since epoch // '1574454240.000000', // End timestamp of the interval // '0.020970', // Open price at midnight UTC // '0.020970', // Intraday high price // '0.020970', // Intraday low price // '0.020970', // Closing price at midnight UTC // '0.020970', // Volume weighted average price // '0.08636138', // Accumulated volume today // 1, // Number of trades today // ], // 'ohlc-1', // Channel Name of subscription // 'ETH/XBT', // Asset pair // ] // const info = this.safeValue (subscription, 'subscription', {}); const interval = this.safeInteger (info, 'interval'); const name = this.safeString (info, 'name'); const wsName = this.safeString (message, 3); const market = this.safeValue (this.options['marketsByWsName'], wsName); const symbol = market['symbol']; const timeframe = this.findTimeframe (interval); const duration = this.parseTimeframe (timeframe); if (timeframe !== undefined) { const candle = this.safeValue (message, 1); const messageHash = name + ':' + timeframe + ':' + wsName; let timestamp = this.safeFloat (candle, 1); timestamp -= duration; const result = [ parseInt (timestamp * 1000), this.safeFloat (candle, 2), this.safeFloat (candle, 3), this.safeFloat (candle, 4), this.safeFloat (candle, 5), this.safeFloat (candle, 7), ]; this.ohlcvs[symbol] = this.safeValue (this.ohlcvs, symbol, {}); let stored = this.safeValue (this.ohlcvs[symbol], timeframe); if (stored === undefined) { const limit = this.safeInteger (this.options, 'OHLCVLimit', 1000); stored = new ArrayCacheByTimestamp (limit); this.ohlcvs[symbol][timeframe] = stored; } stored.append (result); client.resolve (stored, messageHash); } } requestId () { // their support said that reqid must be an int32, not documented const reqid = this.sum (this.safeInteger (this.options, 'reqid', 0), 1); this.options['reqid'] = reqid; return reqid; } async watchPublic (name, symbol, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const wsName = this.safeValue (market['info'], 'wsname'); const messageHash = name + ':' + wsName; const url = this.urls['api']['ws']['public']; const requestId = this.requestId (); const subscribe = { 'event': 'subscribe', 'reqid': requestId, 'pair': [ wsName, ], 'subscription': { 'name': name, }, }; const request = this.deepExtend (subscribe, params); return await this.watch (url, messageHash, request, messageHash); } async watchTicker (symbol, params = {}) { /** * @method * @name kraken#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 kraken api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure} */ return await this.watchPublic ('ticker', symbol, params); } async watchTrades (symbol, since = undefined, limit = undefined, params = {}) { /** * @method * @name kraken#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 kraken api endpoint * @returns {[object]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html?#public-trades} */ await this.loadMarkets (); symbol = this.symbol (symbol); const name = 'trade'; const trades = await this.watchPublic (name, symbol, params); if (this.newUpdates) { limit = trades.getLimit (symbol, limit); } return this.filterBySinceLimit (trades, since, limit, 'timestamp', true); } async watchOrderBook (symbol, limit = undefined, params = {}) { /** * @method * @name kraken#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 kraken 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 name = 'book'; const request = {}; if (limit !== undefined) { if ((limit === 10) || (limit === 25) || (limit === 100) || (limit === 500) || (limit === 1000)) { request['subscription'] = { 'depth': limit, // default 10, valid options 10, 25, 100, 500, 1000 }; } else { throw new NotSupported (this.id + ' watchOrderBook accepts limit values of 10, 25, 100, 500 and 1000 only'); } } const orderbook = await this.watchPublic (name, symbol, this.extend (request, params)); return orderbook.limit (); } async watchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name kraken#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 kraken api endpoint * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume */ await this.loadMarkets (); const name = 'ohlc'; const market = this.market (symbol); symbol = market['symbol']; const wsName = this.safeValue (market['info'], 'wsname'); const messageHash = name + ':' + timeframe + ':' + wsName; const url = this.urls['api']['ws']['public']; const requestId = this.requestId (); const subscribe = { 'event': 'subscribe', 'reqid': requestId, 'pair': [ wsName, ], 'subscription': { 'name': name, 'interval': this.timeframes[timeframe], }, }; const request = this.deepExtend (subscribe, params); const ohlcv = await this.watch (url, messageHash, request, messageHash); if (this.newUpdates) { limit = ohlcv.getLimit (symbol, limit); } return this.filterBySinceLimit (ohlcv, since, limit, 0, true); } async loadMarkets (reload = false, params = {}) { const markets = await super.loadMarkets (reload, params); let marketsByWsName = this.safeValue (this.options, 'marketsByWsName'); if ((marketsByWsName === undefined) || reload) { marketsByWsName = {}; for (let i = 0; i < this.symbols.length; i++) { const symbol = this.symbols[i]; const market = this.markets[symbol]; if (market['darkpool']) { const info = this.safeValue (market, 'info', {}); const altname = this.safeString (info, 'altname'); const wsName = altname.slice (0, 3) + '/' + altname.slice (3); marketsByWsName[wsName] = market; } else { const info = this.safeValue (market, 'info', {}); const wsName = this.safeString (info, 'wsname'); marketsByWsName[wsName] = market; } } this.options['marketsByWsName'] = marketsByWsName; } return markets; } async watchHeartbeat (params = {}) { await this.loadMarkets (); const event = 'heartbeat'; const url = this.urls['api']['ws']['public']; return await this.watch (url, event); } handleHeartbeat (client, message) { // // every second (approx) if no other updates are sent // // { "event": "heartbeat" } // const event = this.safeString (message, 'event'); client.resolve (message, event); } handleOrderBook (client, message, subscription) { // // first message (snapshot) // // [ // 1234, // channelID // { // "as": [ // [ "5541.30000", "2.50700000", "1534614248.123678" ], // [ "5541.80000", "0.33000000", "1534614098.345543" ], // [ "5542.70000", "0.64700000", "1534614244.654432" ] // ], // "bs": [ // [ "5541.20000", "1.52900000", "1534614248.765567" ], // [ "5539.90000", "0.30000000", "1534614241.769870" ], // [ "5539.50000", "5.00000000", "1534613831.243486" ] // ] // }, // "book-10", // "XBT/USD" // ] // // subsequent updates // // [ // 1234, // { // optional // "a": [ // [ "5541.30000", "2.50700000", "1534614248.456738" ], // [ "5542.50000", "0.40100000", "1534614248.456738" ] // ] // }, // { // optional // "b": [ // [ "5541.30000", "0.00000000", "1534614335.345903" ] // ] // }, // "book-10", // "XBT/USD" // ] // const messageLength = message.length; const wsName = message[messageLength - 1]; const bookDepthString = message[messageLength - 2]; const parts = bookDepthString.split ('-'); const depth = this.safeInteger (parts, 1, 10); const market = this.safeValue (this.options['marketsByWsName'], wsName); const symbol = market['symbol']; let timestamp = undefined; const messageHash = 'book:' + wsName; // if this is a snapshot if ('as' in message[1]) { // todo get depth from marketsByWsName this.orderbooks[symbol] = this.orderBook ({}, depth); const orderbook = this.orderbooks[symbol]; const sides = { 'as': 'asks', 'bs': 'bids', }; const keys = Object.keys (sides); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const side = sides[key]; const bookside = orderbook[side]; const deltas = this.safeValue (message[1], key, []); timestamp = this.handleDeltas (bookside, deltas, timestamp); } orderbook['symbol'] = symbol; orderbook['timestamp'] = timestamp; orderbook['datetime'] = this.iso8601 (timestamp); client.resolve (orderbook, messageHash); } else { const orderbook = this.orderbooks[symbol]; // else, if this is an orderbook update let a = undefined; let b = undefined; let c = undefined; if (messageLength === 5) { a = this.safeValue (message[1], 'a', []); b = this.safeValue (message[2], 'b', []); c = this.safeInteger (message[1], 'c'); c = this.safeInteger (message[2], 'c', c); } else { c = this.safeInteger (message[1], 'c'); if ('a' in message[1]) { a = this.safeValue (message[1], 'a', []); } else { b = this.safeValue (message[1], 'b', []); } } const storedAsks = orderbook['asks']; const storedBids = orderbook['bids']; let example = undefined; if (a !== undefined) { timestamp = this.handleDeltas (storedAsks, a, timestamp); example = this.safeValue (a, 0); } if (b !== undefined) { timestamp = this.handleDeltas (storedBids, b, timestamp); example = this.safeValue (b, 0); } // don't remove this line or I will poop on your face orderbook.limit (); const checksum = this.safeValue (this.options, 'checksum', true); if (checksum) { const priceString = this.safeString (example, 0); const amountString = this.safeString (example, 1); const priceParts = priceString.split ('.'); const amountParts = amountString.split ('.'); const priceLength = priceParts[1].length - 0; const amountLength = amountParts[1].length - 0; const payloadArray = []; if (c !== undefined) { for (let i = 0; i < 10; i++) { const formatted = this.formatNumber (storedAsks[i][0], priceLength) + this.formatNumber (storedAsks[i][1], amountLength); payloadArray.push (formatted); } for (let i = 0; i < 10; i++) { const formatted = this.formatNumber (storedBids[i][0], priceLength) + this.formatNumber (storedBids[i][1], amountLength); payloadArray.push (formatted); } } const payload = payloadArray.join (''); const localChecksum = this.crc32 (payload, false); if (localChecksum !== c) { const error = new InvalidNonce (this.id + ' invalid checksum'); client.reject (error, messageHash); } } orderbook['symbol'] = symbol; orderbook['timestamp'] = timestamp; orderbook['datetime'] = this.iso8601 (timestamp); client.resolve (orderbook, messageHash); } } formatNumber (n, length) { const string = this.numberToString (n); const parts = string.split ('.'); const integer = this.safeString (parts, 0); const decimals = this.safeString (parts, 1, ''); const paddedDecimals = decimals.padEnd (length, '0'); const joined = integer + paddedDecimals; let i = 0; while (joined[i] === '0') { i += 1; } if (i > 0) { return joined.slice (i); } else { return joined; } } handleDeltas (bookside, deltas, timestamp) { for (let j = 0; j < deltas.length; j++) { const delta = deltas[j]; const price = parseFloat (delta[0]); const amount = parseFloat (delta[1]); const oldTimestamp = timestamp ? timestamp : 0; timestamp = Math.max (oldTimestamp, parseInt (parseFloat (delta[2]) * 1000)); bookside.store (price, amount); } return timestamp; } handleSystemStatus (client, message) { // // todo: answer the question whether handleSystemStatus should be renamed // and unified as handleStatus for any usage pattern that // involves system status and maintenance updates // // { // connectionID: 15527282728335292000, // event: 'systemStatus', // status: 'online', // online|maintenance|(custom status tbd) // version: '0.2.0' // } // return message; } async authenticate (params = {}) { const url = this.urls['api']['ws']['private']; const client = this.client (url); const authenticated = 'authenticated'; let subscription = this.safeValue (client.subscriptions, authenticated); if (subscription === undefined) { const response = await this.privatePostGetWebSocketsToken (params); // // { // "error":[], // "result":{ // "token":"xeAQ\/RCChBYNVh53sTv1yZ5H4wIbwDF20PiHtTF+4UI", // "expires":900 // } // } // subscription = this.safeValue (response, 'result'); client.subscriptions[authenticated] = subscription; } return this.safeString (subscription, 'token'); } async watchPrivate (name, symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const token = await this.authenticate (); const subscriptionHash = name; let messageHash = name; if (symbol !== undefined) { symbol = this.symbol (symbol); messageHash += ':' + symbol; } const url = this.urls['api']['ws']['private']; const requestId = this.requestId (); const subscribe = { 'event': 'subscribe', 'reqid': requestId, 'subscription': { 'name': name, 'token': token, }, }; const request = this.deepExtend (subscribe, params); const result = await this.watch (url, messageHash, request, subscriptionHash); if (this.newUpdates) { limit = result.getLimit (symbol, limit); } return this.filterBySymbolSinceLimit (result, symbol, since, limit, true); } async watchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name kraken#watchMyTrades * @description watches information on multiple trades made by the user * @param {string} symbol unified market symbol of the market orders were made in * @param {int|undefined} since the earliest time in ms to fetch orders for * @param {int|undefined} limit the maximum number of orde structures to retrieve * @param {object} params extra parameters specific to the kraken api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure */ return await this.watchPrivate ('ownTrades', symbol, since, limit, params); } handleMyTrades (client, message, subscription = undefined) { // // [ // [ // { // 'TT5UC3-GOIRW-6AZZ6R': { // cost: '1493.90107', // fee: '3.88415', // margin: '0.00000', // ordertxid: 'OTLAS3-RRHUF-NDWH5A', // ordertype: 'market', // pair: 'XBT/USDT', // postxid: 'TKH2SE-M7IF5-CFI7LT', // price: '6851.50005', // time: '1586822919.335498', // type: 'sell', // vol: '0.21804000' // } // }, // { // 'TIY6G4-LKLAI-Y3GD4A': { // cost: '22.17134', // fee: '0.05765', // margin: '0.00000', // ordertxid: 'ODQXS7-MOLK6-ICXKAA', // ordertype: 'market', // pair: 'ETH/USD', // postxid: 'TKH2SE-M7IF5-CFI7LT', // price: '169.97999', // time: '1586340530.895739', // type: 'buy', // vol: '0.13043500' // } // }, // ], // 'ownTrades', // { sequence: 1 } // ] // const allTrades = this.safeValue (message, 0, []); const allTradesLength = allTrades.length; if (allTradesLength > 0) { if (this.myTrades === undefined) { const limit = this.safeInteger (this.options, 'tradesLimit', 1000); this.myTrades = new ArrayCache (limit); } const stored = this.myTrades; const symbols = {}; for (let i = 0; i < allTrades.length; i++) { const trades = this.safeValue (allTrades, i, {}); const ids = Object.keys (trades); for (let j = 0; j < ids.length; j++) { const id = ids[j]; const trade = trades[id]; const parsed = this.parseWsTrade (this.extend ({ 'id': id }, trade)); stored.append (parsed); const symbol = parsed['symbol']; symbols[symbol] = true; } } const name = 'ownTrades'; client.resolve (this.myTrades, name); const keys = Object.keys (symbols); for (let i = 0; i < keys.length; i++) { const messageHash = name + ':' + keys[i]; client.resolve (this.myTrades, messageHash); } } } parseWsTrade (trade, market = undefined) { // // { // id: 'TIMIRG-WUNNE-RRJ6GT', // injected from outside // ordertxid: 'OQRPN2-LRHFY-HIFA7D', // postxid: 'TKH2SE-M7IF5-CFI7LT', // pair: 'USDCUSDT', // time: 1586340086.457, // type: 'sell', // ordertype: 'market', // price: '0.99860000', // cost: '22.16892001', // fee: '0.04433784', // vol: '22.20000000', // margin: '0.00000000', // misc: '' // } // // { // id: 'TIY6G4-LKLAI-Y3GD4A', // cost: '22.17134', // fee: '0.05765', // margin: '0.00000', // ordertxid: 'ODQXS7-MOLK6-ICXKAA', // ordertype: 'market', // pair: 'ETH/USD', // postxid: 'TKH2SE-M7IF5-CFI7LT', // price: '169.97999', // time: '1586340530.895739', // type: 'buy', // vol: '0.13043500' // } // const wsName = this.safeString (trade, 'pair'); market = this.safeValue (this.options['marketsByWsName'], wsName, market); let symbol = undefined; const orderId = this.safeString (trade, 'ordertxid'); const id = this.safeString2 (trade, 'id', 'postxid'); const timestamp = this.safeTimestamp (trade, 'time'); const side = this.safeString (trade, 'type'); const type = this.safeString (trade, 'ordertype'); const price = this.safeFloat (trade, 'price'); const amount = this.safeFloat (trade, 'vol'); let cost = undefined; let fee = undefined; if ('fee' in trade) { let currency = undefined; if (market !== undefined) { currency = market['quote']; } fee = { 'cost': this.safeFloat (trade, 'fee'), 'currency': currency, }; } if (market !== undefined) { symbol = market['symbol']; } if (price !== undefined) { if (amount !== undefined) { cost = price * amount; } } return { 'id': id, 'order': orderId, 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'type': type, 'side': side, 'takerOrMaker': undefined, 'price': price, 'amount': amount, 'cost': cost, 'fee': fee, }; } async watchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name kraken#watchOrders * @see https://docs.kraken.com/websockets/#message-openOrders * @description watches information on multiple orders made by the user * @param {string|undefined} symbol unified market symbol of the market orders were made in * @param {int|undefined} since the earliest time in ms to fetch orders for * @param {int|undefined} limit the maximum number of orde structures to retrieve * @param {object} params extra parameters specific to the kraken api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure} */ return await this.watchPrivate ('openOrders', symbol, since, limit, params); } handleOrders (client, message, subscription = undefined) { // // [ // [ // { // "OGTT3Y-C6I3P-XRI6HX": { // "cost": "0.00000", // "descr": { // "close": "", // "leverage": "0:1", // "order": "sell 10.00345345 XBT/EUR @ limit 34.50000 with 0:1 leverage", // "ordertype": "limit", // "pair": "XBT/EUR", // "price": "34.50000", // "price2": "0.00000", // "type": "sell" // }, // "expiretm": "0.000000", // "fee": "0.00000", // "limitprice": "34.50000", // "misc": "", // "oflags": "fcib", // "opentm": "0.000000", // "price": "34.50000", // "refid": "OKIVMP-5GVZN-Z2D2UA", // "starttm": "0.000000", // "status": "open", // "stopprice": "0.000000", // "userref": 0, // "vol": "10.00345345", // "vol_exec": "0.00000000" // } // }, // { // "OGTT3Y-C6I3P-XRI6HX": { // "cost": "0.00000", // "descr": { // "close": "", // "leverage": "0:1", // "order": "sell 0.00000010 XBT/EUR @ limit 5334.60000 with 0:1 leverage", // "ordertype": "limit", // "pair": "XBT/EUR", // "price": "5334.60000", // "price2": "0.00000", // "type": "sell" // }, // "expiretm": "0.000000", // "fee": "0.00000", // "limitprice": "5334.60000", // "misc": "", // "oflags": "fcib", // "opentm": "0.000000", // "price": "5334.60000", // "refid": "OKIVMP-5GVZN-Z2D2UA", // "starttm": "0.000000", // "status": "open", // "stopprice": "0.000000", // "userref": 0, // "vol": "0.00000010", // "vol_exec": "0.00000000" // } // }, // ], // "openOrders", // { "sequence": 234 } // ] // // status-change // // [ // [ // { "OGTT3Y-C6I3P-XRI6HX": { "status": "closed" }}, // { "OGTT3Y-C6I3P-XRI6HX": { "status": "closed" }}, // ], // "openOrders", // { "sequence": 59342 } // ] // const allOrders = this.safeValue (message, 0, []); const allOrdersLength = allOrders.length; if (allOrdersLength > 0) { const limit = this.safeInteger (this.options, 'ordersLimit', 1000); if (this.orders === undefined) { this.orders = new ArrayCacheBySymbolById (limit); } const stored = this.orders; const symbols = {}; for (let i = 0; i < allOrders.length; i++) { const orders = this.safeValue (allOrders, i, {}); const ids = Object.keys (orders); for (let j = 0; j < ids.length; j++) { const id = ids[j]; const order = orders[id]; const parsed = this.parseWsOrder (order); parsed['id'] = id; let symbol = undefined; const symbolsByOrderId = this.safeValue (this.options, 'symbolsByOrderId', {}); if (parsed['symbol'] !== undefined) { symbol = parsed['symbol']; symbolsByOrderId[id] = symbol; this.options['symbolsByOrderId'] = symbolsByOrderId; } else { symbol = this.safeString (symbolsByOrderId, id); } const previousOrders = this.safeValue (stored.hashmap, symbol); const previousOrder = this.safeValue (previousOrders, id); let newOrder = parsed; if (previousOrder !== undefined) { const newRawOrder = this.extend (previousOrder['info'], newOrder['info']); newOrder = this.parseWsOrder (newRawOrder); newOrder['id'] = id; } const length = stored.length; if (length === limit && (previousOrder === undefined)) { const first = stored[0]; if (first['id'] in symbolsByOrderId) { delete symbolsByOrderId[first['id']]; } } stored.append (newOrder); symbols[symbol] = true; } } const name = 'openOrders'; client.resolve (this.orders, name); const keys = Object.keys (symbols); for (let i = 0; i < keys.length; i++) { const messageHash = name + ':' + keys[i]; client.resolve (this.orders, messageHash); } } } parseWsOrder (order, market = undefined) { // // createOrder // { // avg_price: '0.00000', // cost: '0.00000', // descr: { // close: null, // leverage: null, // order: 'sell 0.01000000 ETH/USDT @ limit 1900.00000', // ordertype: 'limit', // pair: 'ETH/USDT', // price: '1900.00000', // price2: '0.00000', // type: 'sell' // }, // expiretm: null, // fee: '0.00000', // limitprice: '0.00000', // misc: '', // oflags: 'fciq', // opentm: '1667522705.757622', // refid: null, // starttm: null, // status: 'open', // stopprice: '0.00000', // timeinforce: 'GTC', // userref: 0, // vol: '0.01000000', // vol_exec: '0.00000000' // } // const description = this.safeValue (order, 'descr', {}); const orderDescription = this.safeString (description, 'order'); let side = undefined; let type = undefined; let wsName = undefined; let price = undefined; let amount = undefined; if (orderDescription !== undefined) { const parts = orderDescription.split (' '); side = this.safeString (parts, 0); amount = this.safeFloat (parts, 1); wsName = this.safeString (parts, 2); type = this.safeString (parts, 4); price = this.safeFloat (parts, 5); } side = this.safeString (description, 'type', side); type = this.safeString (description, 'ordertype', type); wsName = this.safeString (description, 'pair', wsName); market = this.safeValue (this.options['marketsByWsName'], wsName, market); let symbol = undefined; const timestamp = this.safeTimestamp (order, 'opentm'); amount = this.safeFloat (order, 'vol', amount); const filled = this.safeFloat (order, 'vol_exec'); let remaining = undefined; if ((amount !== undefined) && (filled !== undefined)) { remaining = amount - filled; } let fee = undefined; const cost = this.safeFloat (order, 'cost'); price = this.safeFloat (description, 'price', price); if ((price === undefined) || (price === 0.0)) { price = this.safeFloat (description, 'price2'); } if ((price === undefined) || (price === 0.0)) { price = this.safeFloat (order, 'price', price); } const average = this.safeFloat2 (order, 'avg_price', 'price'); if (market !== undefined) { symbol = market['symbol']; if ('fee' in order) { const flags = order['oflags']; const feeCost = this.safeFloat (order, 'fee'); fee = { 'cost': feeCost, 'rate': undefined, }; if (flags.indexOf ('fciq') >= 0) { fee['currency'] = market['quote']; } else if (flags.indexOf ('fcib') >= 0) { fee['currency'] = market['base']; } } } const status = this.parseOrderStatus (this.safeString (order, 'status')); let id = this.safeString (order, 'id'); if (id === undefined) { const txid = this.safeValue (order, 'txid'); id = this.safeString (txid, 0); } const clientOrderId = this.safeString (order, 'userref'); const rawTrades = this.safeValue (order, 'trades'); let trades = undefined; if (rawTrades !== undefined) { trades = this.parseTrades (rawTrades, market, undefined, undefined, { 'order': id }); } const stopPrice = this.safeFloat (order, 'stopprice'); return { 'id': id, 'clientOrderId': clientOrderId, 'info': order, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTradeTimestamp': undefined, 'status': status, 'symbol': symbol, 'type': type, 'timeInForce': undefined, 'postOnly': undefined, 'side': side, 'price': price, 'stopPrice': stopPrice, 'cost': cost, 'amount': amount, 'filled': filled, 'average': average, 'remaining': remaining, 'fee': fee, 'trades': trades, }; } handleSubscriptionStatus (client, message) { // // public // // { // channelID: 210, // channelName: 'book-10', // event: 'subscriptionStatus', // reqid: 1574146735269, // pair: 'ETH/XBT', // status: 'subscribed', // subscription: { depth: 10, name: 'book' } // } // // private // // { // channelName: 'openOrders', // event: 'subscriptionStatus', // reqid: 1, // status: 'subscribed', // subscription: { maxratecount: 125, name: 'openOrders' } // } // const channelId = this.safeString (message, 'channelID'); if (channelId !== undefined) { client.subscriptions[channelId] = message; } // const requestId = this.safeString (message, 'reqid'); // if (requestId in client.futures) { // delete client.futures[requestId]; // } } handleErrorMessage (client, message) { // // { // errorMessage: 'Currency pair not in ISO 4217-A3 format foobar', // event: 'subscriptionStatus', // pair: 'foobar', // reqid: 1574146735269, // status: 'error', // subscription: { name: 'ticker' } // } // const errorMessage = this.safeValue (message, 'errorMessage'); if (errorMessage !== undefined) { const requestId = this.safeValue (message, 'reqid'); if (requestId !== undefined) { const broad = this.exceptions['ws']['broad']; const broadKey = this.findBroadlyMatchedKey (broad, errorMessage); let exception = undefined; if (broadKey === undefined) { exception = new ExchangeError (errorMessage); } else { exception = new broad[broadKey] (errorMessage); } client.reject (exception, requestId); return false; } } return true; } handleMessage (client, message) { if (Array.isArray (message)) { const channelId = this.safeString (message, 0); const subscription = this.safeValue (client.subscriptions, channelId, {}); const info = this.safeValue (subscription, 'subscription', {}); const messageLength = message.length; const channelName = this.safeString (message, messageLength - 2); const name = this.safeString (info, 'name'); const methods = { // public 'book': this.handleOrderBook, 'ohlc': this.handleOHLCV, 'ticker': this.handleTicker, 'trade': this.handleTrades, // private 'openOrders': this.handleOrders, 'ownTrades': this.handleMyTrades, }; const method = this.safeValue2 (methods, name, channelName); if (method === undefined) { return message; } else { return method.call (this, client, message, subscription); } } else { if (this.handleErrorMessage (client, message)) { const event = this.safeString (message, 'event'); const methods = { 'heartbeat': this.handleHeartbeat, 'systemStatus': this.handleSystemStatus, 'subscriptionStatus': this.handleSubscriptionStatus, }; const method = this.safeValue (methods, event); if (method === undefined) { return message; } else { return method.call (this, client, message); } } } } };