UNPKG

ccxt

Version:

A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 100+ exchanges

1,042 lines (1,038 loc) • 43.4 kB
'use strict'; var poloniexfutures$1 = require('../poloniexfutures.js'); var errors = require('../base/errors.js'); var Cache = require('../base/ws/Cache.js'); // ---------------------------------------------------------------------------- // --------------------------------------------------------------------------- class poloniexfutures extends poloniexfutures$1 { describe() { return this.deepExtend(super.describe(), { 'has': { 'ws': true, 'cancelAllOrdersWs': false, 'cancelOrdersWs': false, 'cancelOrderWs': false, 'createOrderWs': false, 'editOrderWs': false, 'fetchBalanceWs': false, 'fetchOpenOrdersWs': false, 'fetchOrderWs': false, 'fetchTradesWs': false, 'watchOHLCV': false, 'watchOrderBook': true, 'watchTicker': true, 'watchTickers': false, 'watchTrades': true, 'watchTradesForSymbols': false, 'watchBalance': true, 'watchOrders': true, 'watchMyTrades': false, 'watchPosition': undefined, 'watchPositions': false, }, 'urls': { 'api': { 'ws': 'wss://futures-apiws.poloniex.com/endpoint', }, }, 'options': { 'tradesLimit': 1000, 'ordersLimit': 1000, 'watchTicker': { 'method': '/contractMarket/ticker', // can also be /contractMarket/snapshot }, 'watchOrders': { 'method': '/contractMarket/tradeOrders', // can also be /contractMarket/advancedOrders }, 'watchOrderBook': { 'method': '/contractMarket/level2', 'snapshotDelay': 5, 'snapshotMaxRetries': 3, 'checksum': true, }, 'streamLimit': 5, 'streamBySubscriptionsHash': {}, 'streamIndex': -1, }, 'streaming': { 'keepAlive': 30000, 'maxPingPongMisses': 2.0, }, }); } async negotiate(privateChannel, params = {}) { const connectId = privateChannel ? 'private' : 'public'; const urls = this.safeValue(this.options, 'urls', {}); if (connectId in urls) { // return urls[connectId]; const storedFuture = urls[connectId]; return await storedFuture; } // we store an awaitable to the url // so that multiple calls don't asynchronously // fetch different urls and overwrite each other urls[connectId] = this.spawn(this.negotiateHelper, privateChannel, params); this.options['urls'] = urls; const future = urls[connectId]; return await future; } async negotiateHelper(privateChannel, params = {}) { let response = undefined; const connectId = privateChannel ? 'private' : 'public'; try { if (privateChannel) { response = await this.privatePostBulletPrivate(params); // // { // "code": "200000", // "data": { // "instanceServers": [ // { // "pingInterval": 50000, // "endpoint": "wss://push-private.kucoin.com/endpoint", // "protocol": "websocket", // "encrypt": true, // "pingTimeout": 10000 // } // ], // "token": "2neAiuYvAU61ZDXANAGAsiL4-iAExhsBXZxftpOeh_55i3Ysy2q2LEsEWU64mdzUOPusi34M_wGoSf7iNyEWJ1UQy47YbpY4zVdzilNP-Bj3iXzrjjGlWtiYB9J6i9GjsxUuhPw3BlrzazF6ghq4Lzf7scStOz3KkxjwpsOBCH4=.WNQmhZQeUKIkh97KYgU0Lg==" // } // } // } else { response = await this.publicPostBulletPublic(params); } const data = this.safeValue(response, 'data', {}); const instanceServers = this.safeValue(data, 'instanceServers', []); const firstInstanceServer = this.safeValue(instanceServers, 0); const pingInterval = this.safeInteger(firstInstanceServer, 'pingInterval'); const endpoint = this.safeString(firstInstanceServer, 'endpoint'); const token = this.safeString(data, 'token'); const result = endpoint + '?' + this.urlencode({ 'token': token, 'privateChannel': privateChannel, 'connectId': connectId, }); const client = this.client(result); client.keepAlive = pingInterval; return result; } catch (e) { const future = this.safeValue(this.options['urls'], connectId); future.reject(e); delete this.options['urls'][connectId]; } return undefined; } requestId() { const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1); this.options['requestId'] = requestId; return requestId; } /** * @ignore * @method * @description Connects to a websocket channel * @param {string} name name of the channel and suscriptionHash * @param {bool} isPrivate true for the authenticated url, false for the public url * @param {string} symbol is required for all public channels, not required for private channels (except position) * @param {object} subscription subscription parameters * @param {object} [params] extra parameters specific to the poloniex api * @returns {object} data from the websocket stream */ async subscribe(name, isPrivate, symbol = undefined, subscription = undefined, params = {}) { const url = await this.negotiate(isPrivate); if (symbol !== undefined) { const market = this.market(symbol); const marketId = market['id']; name += ':' + marketId; } const messageHash = name; const tunnelId = await this.stream(url, messageHash); const requestId = this.requestId(); const subscribe = { 'id': requestId, 'type': 'subscribe', 'topic': name, 'privateChannel': isPrivate, 'response': true, 'tunnelId': tunnelId, }; const subscriptionRequest = { 'id': requestId, }; if (subscription === undefined) { subscription = subscriptionRequest; } else { subscription = this.extend(subscriptionRequest, subscription); } const request = this.extend(subscribe, params); return await this.watch(url, messageHash, request, name, subscriptionRequest); } onClose(client, error) { this.options['streamBySubscriptionsHash'] = {}; super.onClose(client, error); } async stream(url, subscriptionHash) { const streamBySubscriptionsHash = this.safeValue(this.options, 'streamBySubscriptionsHash', {}); let stream = this.safeString(streamBySubscriptionsHash, subscriptionHash); if (stream === undefined) { let streamIndex = this.safeInteger(this.options, 'streamIndex', -1); const streamLimit = this.safeValue(this.options, 'streamLimit'); streamIndex = streamIndex + 1; const normalizedIndex = streamIndex % streamLimit; this.options['streamIndex'] = streamIndex; const streamIndexString = this.numberToString(normalizedIndex); stream = 'stream-' + streamIndexString; this.options['streamBySubscriptionsHash'][subscriptionHash] = stream; const messageHash = 'tunnel:' + stream; const request = { 'id': messageHash, 'type': 'openTunnel', 'newTunnelId': stream, 'response': true, }; const subscription = { 'id': messageHash, 'method': this.handleNewStream, }; await this.watch(url, messageHash, request, messageHash, subscription); } return stream; } handleOrderBookSubscription(client, message, subscription) { const symbol = this.safeString(subscription, 'symbol'); const limit = this.safeInteger(subscription, 'limit'); this.orderbooks[symbol] = this.orderBook({}, limit); } handleSubscriptionStatus(client, message) { // // { // "id": "1578090438322", // "type": "ack" // } // const id = this.safeString(message, 'id'); const subscriptionsById = this.indexBy(client.subscriptions, 'id'); const subscription = this.safeValue(subscriptionsById, id, {}); const method = this.safeValue(subscription, 'method'); if (method !== undefined) { method.call(this, client, message, subscription); } return message; } handleNewStream(client, message, subscription) { // // { // "id": "1545910840805", // "type": "ack" // } // const messageHash = this.safeString(message, 'id'); client.resolve(message, messageHash); } /** * @method * @name poloniexfutures#watchTicker * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @see https://api-docs.poloniex.com/futures/websocket/public#get-real-time-symbol-ticker * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async watchTicker(symbol, params = {}) { await this.loadMarkets(); symbol = this.symbol(symbol); const name = '/contractMarket/ticker'; return await this.subscribe(name, false, symbol, undefined, params); } /** * @method * @name poloniexfutures#watchTrades * @description get the list of most recent trades for a particular symbol * @see https://api-docs.poloniex.com/futures/websocket/public#full-matching-engine-datalevel-3 * @param {string} 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 {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades} */ async watchTrades(symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); const options = this.safeValue(this.options, 'watchTrades'); let name = this.safeString(options, 'method', '/contractMarket/execution'); // can also be /contractMarket/snapshot [name, params] = this.handleOptionAndParams(params, 'method', 'name', name); symbol = this.symbol(symbol); const trades = await this.subscribe(name, false, symbol, undefined, params); if (this.newUpdates) { limit = trades.getLimit(symbol, limit); } return this.filterBySinceLimit(trades, since, limit, 'timestamp', true); } /** * @method * @name poloniexfutures#watchOrderBook * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://api-docs.poloniex.com/futures/websocket/public#level-2-market-data * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int} [limit] not used by poloniexfutures watchOrderBook * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {string} [params.method] the method to use. Defaults to /contractMarket/level2 can also be /contractMarket/level3v2 to receive the raw stream of orders * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async watchOrderBook(symbol, limit = undefined, params = {}) { await this.loadMarkets(); const options = this.safeValue(this.options, 'watchOrderBook'); let name = this.safeString(options, 'method', '/contractMarket/level2'); // can also be /contractMarket/level2, /contractMarket/level2Depth5:{symbol}, /contractMarket/level2Depth50:{symbol} [name, params] = this.handleOptionAndParams(params, 'method', 'name', name); if (name === '/contractMarket/level2' && limit !== undefined) { if (limit !== 5 && limit !== 50) { throw new errors.BadRequest(this.id + ' watchOrderBook limit argument must be none, 5 or 50 if using method /contractMarket/level2'); } name += 'Depth' + this.numberToString(limit); } const subscription = { 'symbol': symbol, 'limit': limit, 'method': this.handleOrderBookSubscription, }; const orderbook = await this.subscribe(name, false, symbol, subscription, params); return orderbook.limit(); } /** * @method * @name poloniexfutures#watchOrders * @description watches information on multiple orders made by the user * @see https://api-docs.poloniex.com/futures/websocket/user-messages#private-messages * @param {string} symbol filter by 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 {object} [params] extra parameters specific to the exchange API endpoint * @param {string} [params.method] the method to use will default to /contractMarket/tradeOrders. Set to /contractMarket/advancedOrders to watch stop orders * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure} */ async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); const options = this.safeValue(this.options, 'watchOrders'); const name = this.safeString(options, 'method', '/contractMarket/tradeOrders'); let orders = await this.subscribe(name, true, undefined, undefined, params); if (this.newUpdates) { limit = orders.getLimit(symbol, limit); } orders = this.filterBySymbolSinceLimit(orders, symbol, since, limit); const length = orders.length; if (length === 0) { return await this.watchOrders(symbol, since, limit, params); } return orders; } /** * @method * @name poloniexfutures#watchBalance * @description watch balance and get the amount of funds available for trading or funds locked in orders * @see https://api-docs.poloniex.com/futures/websocket/user-messages#account-balance-events * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure} */ async watchBalance(params = {}) { await this.loadMarkets(); const name = '/contractAccount/wallet'; return await this.subscribe(name, true, undefined, undefined, params); } handleTrade(client, message) { // // { // "data": { // "makerUserId": "1410336", // "symbol": "BTCUSDTPERP", // "sequence": 267913, // "side": "buy", // "size": 2, // "price": 28409.5, // "takerOrderId": "6426f9f15782c8000776995f", // "makerOrderId": "6426f9f141406b0008df976e", // "takerUserId": "1410880", // "tradeId": "6426f9f1de029f0001e334dd", // "ts": 1680275953739092500, // }, // "subject": "match", // "topic": "/contractMarket/execution:BTCUSDTPERP", // "type": "message", // } // const data = this.safeValue(message, 'data', {}); const marketId = this.safeString(data, 'symbol'); if (marketId !== undefined) { const trade = this.parseWsTrade(data); const symbol = trade['symbol']; const messageHash = '/contractMarket/execution:' + marketId; let stored = this.safeValue(this.trades, symbol); if (stored === undefined) { const tradesLimit = this.safeInteger(this.options, 'tradesLimit', 1000); stored = new Cache.ArrayCache(tradesLimit); this.trades[symbol] = stored; } stored.append(trade); client.resolve(stored, messageHash); } return message; } parseWsTrade(trade, market = undefined) { // // handleTrade // // { // "makerUserId": "1410880", // "symbol": "BTCUSDTPERP", // "sequence": 731390, // "side": "sell", // "size": 2, // "price": 29372.4, // "takerOrderId": "644ef0fdd64748000759218a", // "makerOrderId": "644ef0fd25f4a50007f12fc5", // "takerUserId": "1410880", // "tradeId": "644ef0fdde029f0001eec346", // "ts": 1682895101923194000 // } // const marketId = this.safeString(trade, 'symbol'); market = this.safeMarket(marketId, market); const timestamp = this.safeIntegerProduct(trade, 'ts', 0.000001); return this.safeTrade({ 'info': trade, 'id': this.safeString(trade, 'tradeId'), 'symbol': this.safeString(market, 'symbol'), 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'order': this.safeString2(trade, 'takerOrderId', 'makerOrderId'), 'type': undefined, 'side': this.safeString(trade, 'side'), 'takerOrMaker': undefined, 'price': this.safeString(trade, 'price'), 'amount': this.safeString2(trade, 'matchSize', 'size'), 'cost': undefined, 'fee': undefined, }, market); } parseWsOrderTrade(trade, market = undefined) { // // { // "symbol": "BTC_USDT", // "type": "LIMIT", // "quantity": "1", // "orderId": "32471407854219264", // "tradeFee": "0", // "clientOrderId": "", // "accountType": "SPOT", // "feeCurrency": "", // "eventType": "place", // "source": "API", // "side": "BUY", // "filledQuantity": "0", // "filledAmount": "0", // "matchRole": "MAKER", // "state": "NEW", // "tradeTime": 0, // "tradeAmount": "0", // "orderAmount": "0", // "createTime": 1648708186922, // "price": "47112.1", // "tradeQty": "0", // "tradePrice": "0", // "tradeId": "0", // "ts": 1648708187469 // } // const timestamp = this.safeInteger(trade, 'tradeTime'); const marketId = this.safeString(trade, 'symbol'); return this.safeTrade({ 'info': trade, 'id': this.safeString(trade, 'tradeId'), 'symbol': this.safeSymbol(marketId, market), 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'order': this.safeString(trade, 'orderId'), 'type': this.safeStringLower(trade, 'type'), 'side': this.safeString(trade, 'side'), 'takerOrMaker': this.safeStringLower(trade, 'matchRole'), 'price': this.safeString(trade, 'price'), 'amount': this.safeString(trade, 'tradeAmount'), 'cost': undefined, 'fee': { 'rate': undefined, 'cost': this.safeString(trade, 'tradeFee'), 'currency': this.safeString(trade, 'feeCurrency'), }, }, market); } handleOrder(client, message) { // // { // "data": { // "symbol": "ADAUSDTPERP", // "orderType": "limit", // "side": "buy", // "canceledSize": "1", // "orderId": "642b4d4c0494cd0007c76813", // "type": "canceled", // "orderTime": "1680559436101909048", // "size": "1", // "filledSize": "0", // "marginType": 1, // "price": "0.25", // "remainSize": "0", // "clientOid": "112cbbf1-95a3-4917-957c-d3a87d81f853", // "status": "done", // "ts": 1680559677560686600 // }, // "subject": "orderChange", // "topic": "/contractMarket/tradeOrders", // "channelType": "private", // "type": "message", // "userId": "1139790" // } // stop order // { // "data": { // "orderType": "stop", // "symbol": "BTCUSDTPERP", // "side": "buy", // "stopPriceType": "TP", // "orderId": "64514fe1850d2100074378f6", // "type": "open", // "createdAt": 1683050465847, // "stopPrice": "29000", // "size": 2, // "stop": "up", // "marginType": 0, // "orderPrice": "28552.9", // "ts": 1683050465847597300 // }, // "subject": "stopOrder", // "topic": "/contractMarket/advancedOrders", // "channelType": "private", // "id": "64514fe1850d2100074378fa", // "type": "message", // "userId": "1160396" // } // const data = this.safeValue(message, 'data', {}); let orders = this.orders; if (orders === undefined) { const limit = this.safeInteger(this.options, 'ordersLimit'); orders = new Cache.ArrayCacheBySymbolById(limit); this.orders = orders; } const messageHash = '/contractMarket/tradeOrders'; const parsed = this.parseWsOrder(data); orders.append(parsed); client.resolve(orders, messageHash); return message; } parseOrderStatus(status, type) { /** * @ignore * @method * @param {string} status "match", "open", "done" * @param {string} type "open", "match", "filled", "canceled", "update" * @returns {string} */ const types = { 'canceled': 'canceled', 'cancel': 'canceled', 'filled': 'closed', }; let parsedStatus = this.safeString(types, type); if (parsedStatus === undefined) { const statuses = { 'open': 'open', 'match': 'open', 'done': 'closed', }; parsedStatus = this.safeString(statuses, status, status); } return parsedStatus; } parseWsOrder(order, market = undefined) { // // { // "symbol": "ADAUSDTPERP", // "orderType": "limit", // "side": "buy", // "canceledSize": "1", // "orderId": "642b4d4c0494cd0007c76813", // "type": "canceled", // "orderTime": "1680559436101909048", // "size": "1", // "filledSize": "0", // "marginType": 1, // "price": "0.25", // "remainSize": "0", // "clientOid": "112cbbf1-95a3-4917-957c-d3a87d81f853", // "status": "done", // "ts": 1680559677560686600 // } // stop // { // "orderType": "stop", // "symbol": "BTCUSDTPERP", // "side": "buy", // "stopPriceType": "TP", // "orderId": "64514fe1850d2100074378f6", // "type": "open", // "createdAt": 1683050465847, // "stopPrice": "29000", // "size": 2, // "stop": "up", // "marginType": 0, // "orderPrice": "28552.9", // "ts": 1683050465847597300 // } // const id = this.safeString(order, 'orderId'); const clientOrderId = this.safeString(order, 'clientOid'); const marketId = this.safeString(order, 'symbol'); const timestamp = this.safeIntegerProduct2(order, 'orderTime', 'ts', 0.000001); const status = this.safeString(order, 'status'); const messageType = this.safeString(order, 'type'); return this.safeOrder({ 'info': order, 'symbol': this.safeSymbol(marketId, market), 'id': id, 'clientOrderId': clientOrderId, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'lastTradeTimestamp': undefined, 'type': this.safeString(order, 'orderType'), 'timeInForce': undefined, 'postOnly': undefined, 'side': this.safeString(order, 'side'), 'price': this.safeString2(order, 'price', 'orderPrice'), 'stopPrice': this.safeString(order, 'stopPrice'), 'triggerPrice': undefined, 'amount': this.safeString(order, 'size'), 'cost': undefined, 'average': undefined, 'filled': this.safeString(order, 'filledSize'), 'remaining': this.safeString(order, 'remainSize'), 'status': this.parseOrderStatus(status, messageType), 'fee': undefined, 'trades': undefined, }); } handleTicker(client, message) { // // { // "subject": "ticker", // "topic": "/contractMarket/ticker:BTCUSDTPERP", // "data": { // "symbol": "BTCUSDTPERP", // Market of the symbol // "sequence": 45, // Sequence number which is used to judge the continuity of the pushed messages // "side": "sell", // Transaction side of the last traded taker order // "price": 3600.00, // Filled price // "size": 16, // Filled quantity // "tradeId": "5c9dcf4170744d6f5a3d32fb", // Order ID // "bestBidSize": 795, // Best bid size // "bestBidPrice": 3200.00, // Best bid // "bestAskPrice": 3600.00, // Best ask size // "bestAskSize": 284, // Best ask // "ts": 1553846081210004941 // Filled time - nanosecond // }, // "type": "message", // } // // { // "topic": "/contractMarket/snapshot:BTCUSDTPERP", // "subject": "snapshot.24h", // "data": { // "volume": 30449670, //24h Volume // "turnover": 845169919063, //24h Turnover // "lastPrice": 3551, //Last price // "priceChgPct": 0.0043, //24h Change // "ts": 1547697294838004923 //Snapshot time (nanosecond) // } // } // const data = this.safeValue(message, 'data', {}); const messageHash = this.safeString(message, 'topic'); const symbol = this.getSymbolFromTopic(messageHash); if (symbol !== undefined) { const ticker = this.parseTicker(data); this.tickers[symbol] = ticker; client.resolve(ticker, messageHash); } return message; } handleL3OrderBook(client, message) { // // { // "data": { // "symbol": "BTCUSDTPERP", // "sequence": 1679593048010, // "orderId": "6426fec8586b9500089d64d8", // "clientOid": "14e6ee8e-8757-462c-84db-ed12c2b62f55", // "ts": 1680277192127513900 // }, // "subject": "received", // "topic": "/contractMarket/level3v2:BTCUSDTPERP", // "type": "message" // } // // { // "data": { // "symbol": "BTCUSDTPERP", // "sequence": 1679593047982, // "side": "sell", // "orderTime": "1680277191900131371", // "size": "1", // "orderId": "6426fec7d32b6e000790268b", // "price": "28376.4", // "ts": 1680277191939042300 // }, // "subject": "open", // "topic": "/contractMarket/level3v2:BTCUSDTPERP", // "type": "message" // } // // { // "data": { // "symbol": "BTCUSDTPERP", // "reason": "canceled", // or "filled" // "sequence": 1679593047983, // "orderId": "6426fec74026fa0008e7046f", // "ts": 1680277191949842000 // }, // "subject": "done", // "topic": "/contractMarket/level3v2:BTCUSDTPERP", // "type": "message" // } // const messageHash = this.safeString(message, 'topic'); const subject = this.safeString(message, 'subject'); if (subject === 'received') { return; } // At the time of writting this, there is no implementation to easily convert each order into the orderbook so raw messages are returned client.resolve(message, messageHash); } handleLevel2(client, message) { // { // "subject": "level2", // "topic": "/contractMarket/level2:BTCUSDTPERP", // "type": "message", // "data": { // "sequence": 18, // Sequence number which is used to judge the continuity of pushed messages // "change": "5000.0,sell,83" // Price, side, quantity // "timestamp": 1551770400000 // } // } const topic = this.safeString(message, 'topic'); const isSnapshot = topic.indexOf('Depth') >= 0; if (isSnapshot) { this.handeL2Snapshot(client, message); return; } this.handleL2OrderBook(client, message); } handleL2OrderBook(client, message) { // // { // "id": 1545910660740, // "type": "subscribe", // "topic": "/contractMarket/level2:BTCUSDTPERP", // "response": true // } // // { // "subject": "level2", // "topic": "/contractMarket/level2:BTCUSDTPERP", // "type": "message", // "data": { // "sequence": 18, // Sequence number which is used to judge the continuity of pushed messages // "change": "5000.0,sell,83" // Price, side, quantity // "timestamp": 1551770400000 // } // } // const data = this.safeValue(message, 'data', {}); const messageHash = this.safeString(message, 'topic', ''); const symbol = this.getSymbolFromTopic(messageHash); let orderBook = this.safeValue(this.orderbooks, symbol); if (orderBook === undefined) { this.orderbooks[symbol] = this.orderBook({}); orderBook = this.orderbooks[symbol]; orderBook['symbol'] = symbol; } const nonce = this.safeInteger(orderBook, 'nonce'); if (nonce === undefined) { const cacheLength = orderBook.cache.length; const snapshotDelay = this.handleOption('watchOrderBook', 'snapshotDelay', 5); if (cacheLength === snapshotDelay) { const limit = 0; this.spawn(this.loadOrderBook, client, messageHash, symbol, limit, {}); } orderBook.cache.push(data); return; } try { this.handleDelta(orderBook, data); client.resolve(orderBook, messageHash); } catch (e) { delete this.orderbooks[symbol]; client.reject(e, messageHash); } } handeL2Snapshot(client, message) { // // { // "type": "message", // "topic": "/contractMarket/level2Depth5:BTCUSDTPERP", // "subject": "level2", // "data": { // "asks": [ // ["9993", "3"], // ["9992", "3"], // ["9991", "47"], // ["9990", "32"], // ["9989", "8"] // ], // "bids": [ // ["9988", "56"], // ["9987", "15"], // ["9986", "100"], // ["9985", "10"], // ["9984", "10"] // ], // "timestamp": 1682993050531, // } // } // const data = this.safeValue(message, 'data', {}); const messageHash = this.safeString(message, 'topic', ''); const symbol = this.getSymbolFromTopic(messageHash); const timestamp = this.safeInteger(data, 'timestamp'); const snapshot = this.parseOrderBook(data, symbol, timestamp, 'bids', 'asks'); const orderbook = this.orderBook(snapshot); this.orderbooks[symbol] = orderbook; client.resolve(orderbook, messageHash); } getSymbolFromTopic(topic) { const splitTopic = topic.split(':'); const marketId = this.safeString(splitTopic, 1); return this.safeSymbol(marketId); } getCacheIndex(orderbook, cache) { const firstDelta = this.safeValue(cache, 0); const nonce = this.safeInteger(orderbook, 'nonce'); const firstDeltaSequence = this.safeInteger(firstDelta, 'sequence'); if (firstDeltaSequence > nonce + 1) { return -1; } for (let i = 0; i < cache.length; i++) { const delta = cache[i]; const sequence = this.safeInteger(delta, 'sequence'); if (nonce === sequence - 1) { return i; } } return cache.length; } handleDelta(orderbook, delta) { // // { // sequence: 123677914, // lastSequence: 123677913, // change: '80.36,buy,4924', // changes: [ '80.19,buy,0',"80.15,buy,10794" ], // timestamp: 1715643483528 // }, // const sequence = this.safeInteger(delta, 'sequence'); const lastSequence = this.safeInteger(delta, 'lastSequence'); const nonce = this.safeInteger(orderbook, 'nonce'); if (nonce > sequence) { return; } if (nonce !== lastSequence) { const checksum = this.handleOption('watchOrderBook', 'checksum', true); if (checksum) { throw new errors.ChecksumError(this.id + ' ' + this.orderbookChecksumMessage('')); } } const changes = this.safeList(delta, 'changes'); for (let i = 0; i < changes.length; i++) { const change = changes[i]; const splitChange = change.split(','); const price = this.safeNumber(splitChange, 0); const side = this.safeString(splitChange, 1); const size = this.safeNumber(splitChange, 2); const orderBookSide = (side === 'buy') ? orderbook['bids'] : orderbook['asks']; orderBookSide.store(price, size); } const timestamp = this.safeInteger(delta, 'timestamp'); orderbook['timestamp'] = timestamp; orderbook['datetime'] = this.iso8601(timestamp); orderbook['nonce'] = sequence; } handleBalance(client, message) { // // { // "data": { // "currency": "USDT", // "availableBalance": "4.0000000000", // "timestamp": "1680557568670" // }, // "subject": "availableBalance.change", // "topic": "/contractAccount/wallet", // "channelType": "private", // "id": "642b4600cae86800074b5ab7", // "type": "message", // "userId": "1139790" // } // // { // "data": { // "currency": "USDT", // "orderMargin": "0.0000000000", // "timestamp": "1680558743307" // }, // "subject": "orderMargin.change", // "topic": "/contractAccount/wallet", // "channelType": "private", // "id": "642b4a97b58e360007c3a237", // "type": "message", // "userId": "1139790" // } // const data = this.safeValue(message, 'data', []); const messageHash = '/contractAccount/wallet'; const currencyId = this.safeString(data, 'currency'); const currency = this.currency(currencyId); const code = currency['code']; this.balance[code] = this.parseWsBalance(data); client.resolve(this.balance[code], messageHash); return message; } parseWsBalance(response) { // // { // "currency": "USDT", // "availableBalance": "4.0000000000", // "timestamp": "1680557568670" // } // // { // "currency": "USDT", // "orderMargin": "0.0000000000", // "timestamp": "1680558743307" // } // const timestamp = this.safeInteger(response, 'timestamp'); const result = { 'info': response, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), }; const currencyId = this.safeString(response, 'currency'); const code = this.safeCurrencyCode(currencyId); const newAccount = this.account(); newAccount['free'] = this.safeString(response, 'availableBalance'); result[code] = newAccount; return this.safeBalance(result); } handleSystemStatus(client, message) { // // { // "id": "1578090234088", // connectId // "type": "welcome", // } // return message; } handleSubject(client, message) { const subject = this.safeString(message, 'subject'); const methods = { 'auth': this.handleAuthenticate, 'received': this.handleL3OrderBook, 'open': this.handleL3OrderBook, 'update': this.handleL3OrderBook, 'done': this.handleL3OrderBook, 'level2': this.handleLevel2, 'ticker': this.handleTicker, 'snapshot.24h': this.handleTicker, 'match': this.handleTrade, 'orderChange': this.handleOrder, 'stopOrder': this.handleOrder, 'availableBalance.change': this.handleBalance, 'orderMargin.change': this.handleBalance, }; const method = this.safeValue(methods, subject); if (method !== undefined) { method.call(this, client, message); } } ping(client) { const id = this.requestId().toString(); return { 'id': id, 'type': 'ping', }; } handlePong(client, message) { client.lastPong = this.milliseconds(); return message; } handleErrorMessage(client, message) { // // { // "code": 404, // "data": "tunnel stream-0 is not exist", // "id": "3", // "type": "error" // } // client.reject(message); } handleMessage(client, message) { const type = this.safeString(message, 'type'); const methods = { 'welcome': this.handleSystemStatus, 'ack': this.handleSubscriptionStatus, 'message': this.handleSubject, 'pong': this.handlePong, 'error': this.handleErrorMessage, }; const method = this.safeValue(methods, type); if (method !== undefined) { method.call(this, client, message); } } handleAuthenticate(client, message) { // // { // "success": true, // "ret_msg": '', // "op": "auth", // "conn_id": "ce3dpomvha7dha97tvp0-2xh" // } // const data = this.safeValue(message, 'data'); const success = this.safeValue(data, 'success'); const messageHash = 'authenticated'; if (success) { client.resolve(message, messageHash); } else { const error = new errors.AuthenticationError(this.id + ' ' + this.json(message)); client.reject(error, messageHash); if (messageHash in client.subscriptions) { delete client.subscriptions[messageHash]; } } return message; } } module.exports = poloniexfutures;