ccxt
Version:
1,042 lines (1,038 loc) • 43.4 kB
JavaScript
'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;