Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

binance: add watchTickers #16159

Merged
merged 9 commits into from Jan 4, 2023
177 changes: 166 additions & 11 deletions js/pro/binance.js
Expand Up @@ -4,7 +4,7 @@

const binanceRest = require ('../binance.js');
const Precise = require ('../base/Precise');
const { ExchangeError } = require ('../base/errors');
const { ExchangeError, ArgumentsRequired } = require ('../base/errors');
const { ArrayCache, ArrayCacheByTimestamp, ArrayCacheBySymbolById } = require ('./base/Cache');

// -----------------------------------------------------------------------------
Expand All @@ -20,7 +20,7 @@ module.exports = class binance extends binanceRest {
'watchOrderBook': true,
'watchOrders': true,
'watchTicker': true,
'watchTickers': false, // for now
'watchTickers': true,
'watchTrades': true,
},
'urls': {
Expand Down Expand Up @@ -64,6 +64,9 @@ module.exports = class binance extends binanceRest {
'watchTicker': {
'name': 'ticker', // ticker = 1000ms L1+OHLCV, bookTicker = real-time L1
},
'watchTickers': {
'name': 'ticker', // ticker or miniTicker or bookTicker
},
'watchBalance': {
'fetchBalanceSnapshot': false, // or true
'awaitBalanceSnapshot': true, // whether to wait for the balance snapshot before providing updates
Expand Down Expand Up @@ -745,12 +748,76 @@ module.exports = class binance extends binanceRest {
return await this.watch (url, messageHash, this.extend (request, params), messageHash, subscribe);
}

handleTicker (client, message) {
//
// 24hr rolling window ticker statistics for a single symbol
// These are NOT the statistics of the UTC day, but a 24hr rolling window for the previous 24hrs
// Update Speed 1000ms
async watchTickers (symbols = undefined, params = {}) {
/**
* @method
* @name binance#watchTickers
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
* @param {Array} symbols unified symbol of the market to fetch the ticker for
* @param {object} params extra parameters specific to the binance api endpoint
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure}
*/
await this.loadMarkets ();
symbols = this.marketSymbols (symbols);
const marketIds = this.marketIds (symbols);
let market = undefined;
if (marketIds !== undefined) {
market = this.safeMarket (marketIds[0]);
}
let type = undefined;
[ type, params ] = this.handleMarketTypeAndParams ('watchTickers', market, params);
const options = this.safeValue (this.options, 'watchTickers', {});
let name = this.safeString (options, 'name', 'ticker');
name = this.safeString (params, 'name', name);
const oriParams = params;
params = this.omit (params, 'name');
let wsParams = [];
const messageHash = '!' + name + '@arr';
if (name === 'bookTicker') {
if (marketIds === undefined) {
throw new ArgumentsRequired (this.id + ' watchTickers() requires symbols for bookTicker');
}
// simulate watchTickers with subscribe multiple individual bookTicker topic
for (let i = 0; i < marketIds.length; i++) {
wsParams.push (marketIds[i].toLowerCase () + '@bookTicker');
}
} else {
wsParams = [
messageHash,
];
}
const url = this.urls['api']['ws'][type] + '/' + this.stream (type, messageHash);
const requestId = this.requestId (url);
const request = {
'method': 'SUBSCRIBE',
'params': wsParams,
'id': requestId,
};
const subscribe = {
'id': requestId,
};
const tickers = await this.watch (url, messageHash, this.extend (request, params), messageHash, subscribe);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sc0Vu I think we need to apply the same logic as we have in Cex, because, for futures Binance does not send all the updates within the same message, so we end up returning an empty array when the message does not contain the subscribed symbol
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the topic and data format are the same for different types, probably need to wait the ticker.

These command worked for me:

node examples/js/cli binanceusdm watchTickers '["MATIC/BUSD"]'
node examples/js/cli binanceusdm watchTickers '["APT/BUSD"]'

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sc0Vu what do you mean by "probably need to wait the ticker." ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carlosmiei I see, you mean user might get empty array in this case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sc0Vu yeah but as I said cex solves this issue in an easy way, can you check it out?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sc0Vu is this ready to be reviewed again?

const result = {};
for (let i = 0; i < tickers.length; i++) {
const ticker = tickers[i];
const tickerSymbol = ticker['symbol'];
if (symbols !== undefined && this.inArray (tickerSymbol, symbols)) {
result[tickerSymbol] = ticker;
}
}
const resultKeys = Object.keys (result);
if (resultKeys.length > 0) {
if (this.newUpdates) {
return result;
}
return this.filterByArray (this.tickers, 'symbol', symbols);
}
return await this.watchTickers (symbols, oriParams);
}

parseWsTicker (message) {
//
// ticker
// {
// e: '24hrTicker', // event type
// E: 1579485598569, // event time
Expand All @@ -777,12 +844,23 @@ module.exports = class binance extends binanceRest {
// n: 163222, // total number of trades
// }
//
// miniTicker
// {
// e: '24hrMiniTicker',
// E: 1671617114585,
// s: 'MOBBUSD',
// c: '0.95900000',
// o: '0.91200000',
// h: '1.04000000',
// l: '0.89400000',
// v: '2109995.32000000',
// q: '2019254.05788000'
// }
//
let event = this.safeString (message, 'e', 'bookTicker');
if (event === '24hrTicker') {
event = 'ticker';
}
const wsMarketId = this.safeStringLower (message, 's');
const messageHash = wsMarketId + '@' + event;
let timestamp = undefined;
const now = this.milliseconds ();
if (event === 'bookTicker') {
Expand All @@ -795,7 +873,7 @@ module.exports = class binance extends binanceRest {
const marketId = this.safeString (message, 's');
const symbol = this.safeSymbol (marketId);
const last = this.safeFloat (message, 'c');
const result = {
const ticker = {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
Expand All @@ -817,8 +895,78 @@ module.exports = class binance extends binanceRest {
'quoteVolume': this.safeFloat (message, 'q'),
'info': message,
};
return ticker;
}

handleTicker (client, message) {
//
// 24hr rolling window ticker statistics for a single symbol
// These are NOT the statistics of the UTC day, but a 24hr rolling window for the previous 24hrs
// Update Speed 1000ms
//
// {
// e: '24hrTicker', // event type
// E: 1579485598569, // event time
// s: 'ETHBTC', // symbol
// p: '-0.00004000', // price change
// P: '-0.209', // price change percent
// w: '0.01920495', // weighted average price
// x: '0.01916500', // the price of the first trade before the 24hr rolling window
// c: '0.01912500', // last (closing) price
// Q: '0.10400000', // last quantity
// b: '0.01912200', // best bid
// B: '4.10400000', // best bid quantity
// a: '0.01912500', // best ask
// A: '0.00100000', // best ask quantity
// o: '0.01916500', // open price
// h: '0.01956500', // high price
// l: '0.01887700', // low price
// v: '173518.11900000', // base volume
// q: '3332.40703994', // quote volume
// O: 1579399197842, // open time
// C: 1579485597842, // close time
// F: 158251292, // first trade id
// L: 158414513, // last trade id
// n: 163222, // total number of trades
// }
//
let event = this.safeString (message, 'e', 'bookTicker');
if (event === '24hrTicker') {
event = 'ticker';
} else if (event === '24hrMiniTicker') {
event = 'miniTicker';
}
const wsMarketId = this.safeStringLower (message, 's');
const messageHash = wsMarketId + '@' + event;
const result = this.parseWsTicker (message);
const symbol = result['symbol'];
this.tickers[symbol] = result;
client.resolve (result, messageHash);
if (event === 'bookTicker') {
// watch bookTickers
client.resolve ([ result ], '!' + 'bookTicker@arr');
}
}

handleTickers (client, message) {
let event = undefined;
for (let i = 0; i < message.length; i++) {
const ticker = message[i];
event = this.safeString (ticker, 'e');
if (event === '24hrTicker') {
event = 'ticker';
} else if (event === '24hrMiniTicker') {
event = 'miniTicker';
}
const wsMarketId = this.safeStringLower (ticker, 's');
const messageHash = wsMarketId + '@' + event;
const result = this.parseWsTicker (ticker);
const symbol = result['symbol'];
this.tickers[symbol] = result;
client.resolve (result, messageHash);
}
const values = Object.values (this.tickers);
client.resolve (values, '!' + event + '@arr');
}

async authenticate (params = {}) {
Expand Down Expand Up @@ -1478,15 +1626,22 @@ module.exports = class binance extends binanceRest {
'trade': this.handleTrade,
'aggTrade': this.handleTrade,
'kline': this.handleOHLCV,
'24hrTicker@arr': this.handleTickers,
'24hrMiniTicker@arr': this.handleTickers,
'24hrTicker': this.handleTicker,
'24hrMiniTicker': this.handleTicker,
'bookTicker': this.handleTicker,
'outboundAccountPosition': this.handleBalance,
'balanceUpdate': this.handleBalance,
'ACCOUNT_UPDATE': this.handleBalance,
'executionReport': this.handleOrderUpdate,
'ORDER_TRADE_UPDATE': this.handleOrderUpdate,
};
const event = this.safeString (message, 'e');
let event = this.safeString (message, 'e');
if (Array.isArray (message)) {
const data = message[0];
event = this.safeString (data, 'e') + '@arr';
}
const method = this.safeValue (methods, event);
if (method === undefined) {
const requestId = this.safeString (message, 'id');
Expand Down