UNPKG

ccxt

Version:

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

943 lines (940 loc) • 218 kB
// ---------------------------------------------------------------------------- // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code // EDIT THE CORRESPONDENT .ts FILE INSTEAD // ---------------------------------------------------------------------------- import binanceRest from '../binance.js'; import { Precise } from '../base/Precise.js'; import { ChecksumError, ArgumentsRequired, BadRequest, NotSupported } from '../base/errors.js'; import { ArrayCache, ArrayCacheByTimestamp, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide } from '../base/ws/Cache.js'; import { sha256 } from '../static_dependencies/noble-hashes/sha256.js'; import { rsa } from '../base/functions/rsa.js'; import { eddsa } from '../base/functions/crypto.js'; import { ed25519 } from '../static_dependencies/noble-curves/ed25519.js'; // ----------------------------------------------------------------------------- export default class binance extends binanceRest { describe() { const superDescribe = super.describe(); return this.deepExtend(superDescribe, this.describeData()); } describeData() { return { 'has': { 'ws': true, 'watchBalance': true, 'watchLiquidations': true, 'watchLiquidationsForSymbols': true, 'watchMyLiquidations': true, 'watchMyLiquidationsForSymbols': true, 'watchBidsAsks': true, 'watchMyTrades': true, 'watchOHLCV': true, 'watchOHLCVForSymbols': true, 'watchOrderBook': true, 'watchOrderBookForSymbols': true, 'watchOrders': true, 'watchOrdersForSymbols': true, 'watchPositions': true, 'watchTicker': true, 'watchTickers': true, 'watchMarkPrices': true, 'watchMarkPrice': true, 'watchTrades': true, 'watchTradesForSymbols': true, 'createOrderWs': true, 'editOrderWs': true, 'cancelOrderWs': true, 'cancelOrdersWs': false, 'cancelAllOrdersWs': true, 'fetchBalanceWs': true, 'fetchDepositsWs': false, 'fetchMarketsWs': false, 'fetchMyTradesWs': true, 'fetchOHLCVWs': true, 'fetchOrderBookWs': true, 'fetchOpenOrdersWs': true, 'fetchOrderWs': true, 'fetchOrdersWs': true, 'fetchPositionWs': true, 'fetchPositionForSymbolWs': true, 'fetchPositionsWs': true, 'fetchTickerWs': true, 'fetchTradesWs': true, 'fetchTradingFeesWs': false, 'fetchWithdrawalsWs': false, }, 'urls': { 'test': { 'ws': { 'spot': 'wss://stream.testnet.binance.vision/ws', 'margin': 'wss://stream.testnet.binance.vision/ws', 'future': 'wss://fstream.binancefuture.com/ws', 'delivery': 'wss://dstream.binancefuture.com/ws', 'ws-api': { 'spot': 'wss://ws-api.testnet.binance.vision/ws-api/v3', 'future': 'wss://testnet.binancefuture.com/ws-fapi/v1', 'delivery': 'wss://testnet.binancefuture.com/ws-dapi/v1', }, }, }, 'api': { 'ws': { 'spot': 'wss://stream.binance.com:9443/ws', 'margin': 'wss://stream.binance.com:9443/ws', 'future': 'wss://fstream.binance.com/ws', 'delivery': 'wss://dstream.binance.com/ws', 'ws-api': { 'spot': 'wss://ws-api.binance.com:443/ws-api/v3', 'future': 'wss://ws-fapi.binance.com/ws-fapi/v1', 'delivery': 'wss://ws-dapi.binance.com/ws-dapi/v1', }, 'papi': 'wss://fstream.binance.com/pm/ws', }, }, 'doc': 'https://developers.binance.com/en', }, 'streaming': { 'keepAlive': 180000, }, 'options': { 'returnRateLimits': false, 'streamLimits': { 'spot': 50, 'margin': 50, 'future': 50, 'delivery': 50, // max 200 }, 'subscriptionLimitByStream': { 'spot': 200, 'margin': 200, 'future': 200, 'delivery': 200, }, 'streamBySubscriptionsHash': this.createSafeDictionary(), 'streamIndex': -1, // get updates every 1000ms or 100ms // or every 0ms in real-time for futures 'watchOrderBookRate': 100, 'liquidationsLimit': 1000, 'myLiquidationsLimit': 1000, 'tradesLimit': 1000, 'ordersLimit': 1000, 'OHLCVLimit': 1000, 'requestId': this.createSafeDictionary(), 'watchOrderBookLimit': 1000, 'watchTrades': { 'name': 'trade', // 'trade' or 'aggTrade' }, 'watchTicker': { 'name': 'ticker', // ticker or miniTicker or ticker_<window_size> }, 'watchTickers': { 'name': 'ticker', // ticker or miniTicker or ticker_<window_size> }, 'watchOHLCV': { 'name': 'kline', // or indexPriceKline or markPriceKline (coin-m futures) }, 'watchOrderBook': { 'maxRetries': 3, 'checksum': true, }, 'watchBalance': { 'fetchBalanceSnapshot': false, 'awaitBalanceSnapshot': true, // whether to wait for the balance snapshot before providing updates }, 'watchLiquidationsForSymbols': { 'defaultType': 'swap', }, 'watchPositions': { 'fetchPositionsSnapshot': true, 'awaitPositionsSnapshot': true, // whether to wait for the positions snapshot before providing updates }, 'wallet': 'wb', 'listenKeyRefreshRate': 1200000, 'ws': { 'cost': 5, }, 'tickerChannelsMap': { '24hrTicker': 'ticker', '24hrMiniTicker': 'miniTicker', 'markPriceUpdate': 'markPrice', // rolling window tickers '1hTicker': 'ticker_1h', '4hTicker': 'ticker_4h', '1dTicker': 'ticker_1d', 'bookTicker': 'bookTicker', }, }, }; } requestId(url) { const options = this.safeDict(this.options, 'requestId', this.createSafeDictionary()); const previousValue = this.safeInteger(options, url, 0); const newValue = this.sum(previousValue, 1); this.options['requestId'][url] = newValue; return newValue; } stream(type, subscriptionHash, numSubscriptions = 1) { const streamBySubscriptionsHash = this.safeDict(this.options, 'streamBySubscriptionsHash', this.createSafeDictionary()); let stream = this.safeString(streamBySubscriptionsHash, subscriptionHash); if (stream === undefined) { let streamIndex = this.safeInteger(this.options, 'streamIndex', -1); const streamLimits = this.safeValue(this.options, 'streamLimits'); const streamLimit = this.safeInteger(streamLimits, type); streamIndex = streamIndex + 1; const normalizedIndex = streamIndex % streamLimit; this.options['streamIndex'] = streamIndex; stream = this.numberToString(normalizedIndex); this.options['streamBySubscriptionsHash'][subscriptionHash] = stream; const subscriptionsByStreams = this.safeValue(this.options, 'numSubscriptionsByStream'); if (subscriptionsByStreams === undefined) { this.options['numSubscriptionsByStream'] = this.createSafeDictionary(); } const subscriptionsByStream = this.safeInteger(this.options['numSubscriptionsByStream'], stream, 0); const newNumSubscriptions = subscriptionsByStream + numSubscriptions; const subscriptionLimitByStream = this.safeInteger(this.options['subscriptionLimitByStream'], type, 200); if (newNumSubscriptions > subscriptionLimitByStream) { throw new BadRequest(this.id + ' reached the limit of subscriptions by stream. Increase the number of streams, or increase the stream limit or subscription limit by stream if the exchange allows.'); } this.options['numSubscriptionsByStream'][stream] = subscriptionsByStream + numSubscriptions; } return stream; } /** * @method * @name binance#watchLiquidations * @description watch the public liquidations of a trading pair * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Liquidation-Order-Streams * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Liquidation-Order-Streams * @param {string} symbol unified CCXT market symbol * @param {int} [since] the earliest time in ms to fetch liquidations for * @param {int} [limit] the maximum number of liquidation structures to retrieve * @param {object} [params] exchange specific parameters for the bitmex api endpoint * @returns {object} an array of [liquidation structures]{@link https://github.com/ccxt/ccxt/wiki/Manual#liquidation-structure} */ async watchLiquidations(symbol, since = undefined, limit = undefined, params = {}) { return await this.watchLiquidationsForSymbols([symbol], since, limit, params); } /** * @method * @name binance#watchLiquidationsForSymbols * @description watch the public liquidations of a trading pair * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/All-Market-Liquidation-Order-Streams * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/All-Market-Liquidation-Order-Streams * @param {string[]} symbols list of unified market symbols * @param {int} [since] the earliest time in ms to fetch liquidations for * @param {int} [limit] the maximum number of liquidation structures to retrieve * @param {object} [params] exchange specific parameters for the bitmex api endpoint * @returns {object} an array of [liquidation structures]{@link https://github.com/ccxt/ccxt/wiki/Manual#liquidation-structure} */ async watchLiquidationsForSymbols(symbols = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); const subscriptionHashes = []; const messageHashes = []; let streamHash = 'liquidations'; symbols = this.marketSymbols(symbols, undefined, true, true); if (this.isEmpty(symbols)) { subscriptionHashes.push('!' + 'forceOrder@arr'); messageHashes.push('liquidations'); } else { for (let i = 0; i < symbols.length; i++) { const market = this.market(symbols[i]); subscriptionHashes.push(market['lowercaseId'] + '@forceOrder'); messageHashes.push('liquidations::' + symbols[i]); } streamHash += '::' + symbols.join(','); } const firstMarket = this.getMarketFromSymbols(symbols); let type = undefined; [type, params] = this.handleMarketTypeAndParams('watchLiquidationsForSymbols', firstMarket, params); if (type === 'spot') { throw new BadRequest(this.id + ' watchLiquidationsForSymbols is not supported for spot symbols'); } let subType = undefined; [subType, params] = this.handleSubTypeAndParams('watchLiquidationsForSymbols', firstMarket, params); if (this.isLinear(type, subType)) { type = 'future'; } else if (this.isInverse(type, subType)) { type = 'delivery'; } const numSubscriptions = subscriptionHashes.length; const url = this.urls['api']['ws'][type] + '/' + this.stream(type, streamHash, numSubscriptions); const requestId = this.requestId(url); const request = { 'method': 'SUBSCRIBE', 'params': subscriptionHashes, 'id': requestId, }; const subscribe = { 'id': requestId, }; const newLiquidations = await this.watchMultiple(url, messageHashes, this.extend(request, params), subscriptionHashes, subscribe); if (this.newUpdates) { return newLiquidations; } return this.filterBySymbolsSinceLimit(this.liquidations, symbols, since, limit, true); } handleLiquidation(client, message) { // // future // { // "e":"forceOrder", // "E":1698871323061, // "o":{ // "s":"BTCUSDT", // "S":"BUY", // "o":"LIMIT", // "f":"IOC", // "q":"1.437", // "p":"35100.81", // "ap":"34959.70", // "X":"FILLED", // "l":"1.437", // "z":"1.437", // "T":1698871323059 // } // } // delivery // { // "e":"forceOrder", // Event Type // "E": 1591154240950, // Event Time // "o":{ // "s":"BTCUSD_200925", // Symbol // "ps": "BTCUSD", // Pair // "S":"SELL", // Side // "o":"LIMIT", // Order Type // "f":"IOC", // Time in Force // "q":"1", // Original Quantity // "p":"9425.5", // Price // "ap":"9496.5", // Average Price // "X":"FILLED", // Order Status // "l":"1", // Order Last Filled Quantity // "z":"1", // Order Filled Accumulated Quantity // "T": 1591154240949, // Order Trade Time // } // } // const rawLiquidation = this.safeValue(message, 'o', {}); const marketId = this.safeString(rawLiquidation, 's'); const market = this.safeMarket(marketId, undefined, '', 'contract'); const symbol = market['symbol']; const liquidation = this.parseWsLiquidation(rawLiquidation, market); let liquidations = this.safeValue(this.liquidations, symbol); if (liquidations === undefined) { const limit = this.safeInteger(this.options, 'liquidationsLimit', 1000); liquidations = new ArrayCache(limit); } liquidations.append(liquidation); this.liquidations[symbol] = liquidations; client.resolve([liquidation], 'liquidations'); client.resolve([liquidation], 'liquidations::' + symbol); } parseWsLiquidation(liquidation, market = undefined) { // // future // { // "s":"BTCUSDT", // "S":"BUY", // "o":"LIMIT", // "f":"IOC", // "q":"1.437", // "p":"35100.81", // "ap":"34959.70", // "X":"FILLED", // "l":"1.437", // "z":"1.437", // "T":1698871323059 // } // delivery // { // "s":"BTCUSD_200925", // Symbol // "ps": "BTCUSD", // Pair // "S":"SELL", // Side // "o":"LIMIT", // Order Type // "f":"IOC", // Time in Force // "q":"1", // Original Quantity // "p":"9425.5", // Price // "ap":"9496.5", // Average Price // "X":"FILLED", // Order Status // "l":"1", // Order Last Filled Quantity // "z":"1", // Order Filled Accumulated Quantity // "T": 1591154240949, // Order Trade Time // } // myLiquidation // { // "s":"BTCUSDT", // Symbol // "c":"TEST", // Client Order Id // // special client order id: // // starts with "autoclose-": liquidation order // // "adl_autoclose": ADL auto close order // // "settlement_autoclose-": settlement order for delisting or delivery // "S":"SELL", // Side // "o":"TRAILING_STOP_MARKET", // Order Type // "f":"GTC", // Time in Force // "q":"0.001", // Original Quantity // "p":"0", // Original Price // "ap":"0", // Average Price // "sp":"7103.04", // Stop Price. Please ignore with TRAILING_STOP_MARKET order // "x":"NEW", // Execution Type // "X":"NEW", // Order Status // "i":8886774, // Order Id // "l":"0", // Order Last Filled Quantity // "z":"0", // Order Filled Accumulated Quantity // "L":"0", // Last Filled Price // "N":"USDT", // Commission Asset, will not push if no commission // "n":"0", // Commission, will not push if no commission // "T":1568879465650, // Order Trade Time // "t":0, // Trade Id // "b":"0", // Bids Notional // "a":"9.91", // Ask Notional // "m":false, // Is this trade the maker side? // "R":false, // Is this reduce only // "wt":"CONTRACT_PRICE", // Stop Price Working Type // "ot":"TRAILING_STOP_MARKET",// Original Order Type // "ps":"LONG", // Position Side // "cp":false, // If Close-All, pushed with conditional order // "AP":"7476.89", // Activation Price, only puhed with TRAILING_STOP_MARKET order // "cr":"5.0", // Callback Rate, only puhed with TRAILING_STOP_MARKET order // "pP": false, // If price protection is turned on // "si": 0, // ignore // "ss": 0, // ignore // "rp":"0", // Realized Profit of the trade // "V":"EXPIRE_TAKER", // STP mode // "pm":"OPPONENT", // Price match mode // "gtd":0 // TIF GTD order auto cancel time // } // const marketId = this.safeString(liquidation, 's'); market = this.safeMarket(marketId, market); const timestamp = this.safeInteger(liquidation, 'T'); return this.safeLiquidation({ 'info': liquidation, 'symbol': this.safeSymbol(marketId, market), 'contracts': this.safeNumber(liquidation, 'l'), 'contractSize': this.safeNumber(market, 'contractSize'), 'price': this.safeNumber(liquidation, 'ap'), 'baseValue': undefined, 'quoteValue': undefined, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), }); } /** * @method * @name binance#watchMyLiquidations * @description watch the private liquidations of a trading pair * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data-streams/Event-Order-Update * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/user-data-streams/Event-Order-Update * @param {string} symbol unified CCXT market symbol * @param {int} [since] the earliest time in ms to fetch liquidations for * @param {int} [limit] the maximum number of liquidation structures to retrieve * @param {object} [params] exchange specific parameters for the bitmex api endpoint * @returns {object} an array of [liquidation structures]{@link https://github.com/ccxt/ccxt/wiki/Manual#liquidation-structure} */ async watchMyLiquidations(symbol, since = undefined, limit = undefined, params = {}) { return this.watchMyLiquidationsForSymbols([symbol], since, limit, params); } /** * @method * @name binance#watchMyLiquidationsForSymbols * @description watch the private liquidations of a trading pair * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data-streams/Event-Order-Update * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/user-data-streams/Event-Order-Update * @param {string[]} symbols list of unified market symbols * @param {int} [since] the earliest time in ms to fetch liquidations for * @param {int} [limit] the maximum number of liquidation structures to retrieve * @param {object} [params] exchange specific parameters for the bitmex api endpoint * @returns {object} an array of [liquidation structures]{@link https://github.com/ccxt/ccxt/wiki/Manual#liquidation-structure} */ async watchMyLiquidationsForSymbols(symbols, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, true, true, true); const market = this.getMarketFromSymbols(symbols); const messageHashes = ['myLiquidations']; if (!this.isEmpty(symbols)) { for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; messageHashes.push('myLiquidations::' + symbol); } } let type = undefined; [type, params] = this.handleMarketTypeAndParams('watchMyLiquidationsForSymbols', market, params); let subType = undefined; [subType, params] = this.handleSubTypeAndParams('watchMyLiquidationsForSymbols', market, params); if (this.isLinear(type, subType)) { type = 'future'; } else if (this.isInverse(type, subType)) { type = 'delivery'; } await this.authenticate(params); const url = this.urls['api']['ws'][type] + '/' + this.options[type]['listenKey']; const message = undefined; const newLiquidations = await this.watchMultiple(url, messageHashes, message, [type]); if (this.newUpdates) { return newLiquidations; } return this.filterBySymbolsSinceLimit(this.liquidations, symbols, since, limit); } handleMyLiquidation(client, message) { // // { // "s":"BTCUSDT", // Symbol // "c":"TEST", // Client Order Id // // special client order id: // // starts with "autoclose-": liquidation order // // "adl_autoclose": ADL auto close order // // "settlement_autoclose-": settlement order for delisting or delivery // "S":"SELL", // Side // "o":"TRAILING_STOP_MARKET", // Order Type // "f":"GTC", // Time in Force // "q":"0.001", // Original Quantity // "p":"0", // Original Price // "ap":"0", // Average Price // "sp":"7103.04", // Stop Price. Please ignore with TRAILING_STOP_MARKET order // "x":"NEW", // Execution Type // "X":"NEW", // Order Status // "i":8886774, // Order Id // "l":"0", // Order Last Filled Quantity // "z":"0", // Order Filled Accumulated Quantity // "L":"0", // Last Filled Price // "N":"USDT", // Commission Asset, will not push if no commission // "n":"0", // Commission, will not push if no commission // "T":1568879465650, // Order Trade Time // "t":0, // Trade Id // "b":"0", // Bids Notional // "a":"9.91", // Ask Notional // "m":false, // Is this trade the maker side? // "R":false, // Is this reduce only // "wt":"CONTRACT_PRICE", // Stop Price Working Type // "ot":"TRAILING_STOP_MARKET",// Original Order Type // "ps":"LONG", // Position Side // "cp":false, // If Close-All, pushed with conditional order // "AP":"7476.89", // Activation Price, only puhed with TRAILING_STOP_MARKET order // "cr":"5.0", // Callback Rate, only puhed with TRAILING_STOP_MARKET order // "pP": false, // If price protection is turned on // "si": 0, // ignore // "ss": 0, // ignore // "rp":"0", // Realized Profit of the trade // "V":"EXPIRE_TAKER", // STP mode // "pm":"OPPONENT", // Price match mode // "gtd":0 // TIF GTD order auto cancel time // } // const orderType = this.safeString(message, 'o'); if (orderType !== 'LIQUIDATION') { return; } const marketId = this.safeString(message, 's'); const market = this.safeMarket(marketId); const symbol = this.safeSymbol(marketId); const liquidation = this.parseWsLiquidation(message, market); let myLiquidations = this.safeValue(this.myLiquidations, symbol); if (myLiquidations === undefined) { const limit = this.safeInteger(this.options, 'myLiquidationsLimit', 1000); myLiquidations = new ArrayCache(limit); } myLiquidations.append(liquidation); this.myLiquidations[symbol] = myLiquidations; client.resolve([liquidation], 'myLiquidations'); client.resolve([liquidation], 'myLiquidations::' + symbol); } /** * @method * @name binance#watchOrderBook * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#partial-book-depth-streams * @see https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#diff-depth-stream * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int} [limit] the maximum amount of order book entries to return * @param {object} [params] extra parameters specific to the exchange API endpoint * @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 = {}) { // // todo add support for <levels>-snapshots (depth) // https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#partial-book-depth-streams // <symbol>@depth<levels>@100ms or <symbol>@depth<levels> (1000ms) // valid <levels> are 5, 10, or 20 // // default 100, max 1000, valid limits 5, 10, 20, 50, 100, 500, 1000 // // notice the differences between trading futures and spot trading // the algorithms use different urls in step 1 // delta caching and merging also differs in steps 4, 5, 6 // // spot/margin // https://binance-docs.github.io/apidocs/spot/en/#how-to-manage-a-local-order-book-correctly // // 1. Open a stream to wss://stream.binance.com:9443/ws/bnbbtc@depth. // 2. Buffer the events you receive from the stream. // 3. Get a depth snapshot from https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000 . // 4. Drop any event where u is <= lastUpdateId in the snapshot. // 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1. // 6. While listening to the stream, each new event's U should be equal to the previous event's u+1. // 7. The data in each event is the absolute quantity for a price level. // 8. If the quantity is 0, remove the price level. // 9. Receiving an event that removes a price level that is not in your local order book can happen and is normal. // // futures // https://binance-docs.github.io/apidocs/futures/en/#how-to-manage-a-local-order-book-correctly // // 1. Open a stream to wss://fstream.binance.com/stream?streams=btcusdt@depth. // 2. Buffer the events you receive from the stream. For same price, latest received update covers the previous one. // 3. Get a depth snapshot from https://fapi.binance.com/fapi/v1/depth?symbol=BTCUSDT&limit=1000 . // 4. Drop any event where u is < lastUpdateId in the snapshot. // 5. The first processed event should have U <= lastUpdateId AND u >= lastUpdateId // 6. While listening to the stream, each new event's pu should be equal to the previous event's u, otherwise initialize the process from step 3. // 7. The data in each event is the absolute quantity for a price level. // 8. If the quantity is 0, remove the price level. // 9. Receiving an event that removes a price level that is not in your local order book can happen and is normal. // return await this.watchOrderBookForSymbols([symbol], limit, params); } /** * @method * @name binance#watchOrderBookForSymbols * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#partial-book-depth-streams * @see https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#diff-depth-stream * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams * @param {string[]} symbols unified array of symbols * @param {int} [limit] the maximum amount of order book entries to return * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async watchOrderBookForSymbols(symbols, limit = undefined, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, false, true, true); const firstMarket = this.market(symbols[0]); let type = firstMarket['type']; if (firstMarket['contract']) { type = firstMarket['linear'] ? 'future' : 'delivery'; } const name = 'depth'; let streamHash = 'multipleOrderbook'; if (symbols !== undefined) { const symbolsLength = symbols.length; if (symbolsLength > 200) { throw new BadRequest(this.id + ' watchOrderBookForSymbols() accepts 200 symbols at most. To watch more symbols call watchOrderBookForSymbols() multiple times'); } streamHash += '::' + symbols.join(','); } const watchOrderBookRate = this.safeString(this.options, 'watchOrderBookRate', '100'); const subParams = []; const messageHashes = []; for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; const market = this.market(symbol); messageHashes.push('orderbook::' + symbol); const subscriptionHash = market['lowercaseId'] + '@' + name; const symbolHash = subscriptionHash + '@' + watchOrderBookRate + 'ms'; subParams.push(symbolHash); } const messageHashesLength = messageHashes.length; const url = this.urls['api']['ws'][type] + '/' + this.stream(type, streamHash, messageHashesLength); const requestId = this.requestId(url); const request = { 'method': 'SUBSCRIBE', 'params': subParams, 'id': requestId, }; const subscription = { 'id': requestId.toString(), 'name': name, 'symbols': symbols, 'method': this.handleOrderBookSubscription, 'limit': limit, 'type': type, 'params': params, }; const orderbook = await this.watchMultiple(url, messageHashes, this.extend(request, params), messageHashes, subscription); return orderbook.limit(); } /** * @method * @name binance#unWatchOrderBookForSymbols * @description unWatches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#partial-book-depth-streams * @see https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#diff-depth-stream * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams * @param {string[]} symbols unified array of symbols * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async unWatchOrderBookForSymbols(symbols, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, false, true, true); const firstMarket = this.market(symbols[0]); let type = firstMarket['type']; if (firstMarket['contract']) { type = firstMarket['linear'] ? 'future' : 'delivery'; } const name = 'depth'; let streamHash = 'multipleOrderbook'; if (symbols !== undefined) { streamHash += '::' + symbols.join(','); } const watchOrderBookRate = this.safeString(this.options, 'watchOrderBookRate', '100'); const subParams = []; const subMessageHashes = []; const messageHashes = []; for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; const market = this.market(symbol); subMessageHashes.push('orderbook::' + symbol); messageHashes.push('unsubscribe:orderbook:' + symbol); const subscriptionHash = market['lowercaseId'] + '@' + name; const symbolHash = subscriptionHash + '@' + watchOrderBookRate + 'ms'; subParams.push(symbolHash); } const messageHashesLength = subMessageHashes.length; const url = this.urls['api']['ws'][type] + '/' + this.stream(type, streamHash, messageHashesLength); const requestId = this.requestId(url); const request = { 'method': 'UNSUBSCRIBE', 'params': subParams, 'id': requestId, }; const subscription = { 'unsubscribe': true, 'id': requestId.toString(), 'symbols': symbols, 'subMessageHashes': subMessageHashes, 'messageHashes': messageHashes, 'topic': 'orderbook', }; return await this.watchMultiple(url, messageHashes, this.extend(request, params), messageHashes, subscription); } /** * @method * @name binance#unWatchOrderBook * @description unWatches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#partial-book-depth-streams * @see https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#diff-depth-stream * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams * @see https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams * @param {string} symbol unified array of symbols * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async unWatchOrderBook(symbol, params = {}) { return await this.unWatchOrderBookForSymbols([symbol], params); } /** * @method * @name binance#fetchOrderBookWs * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#order-book * @see https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/websocket-api/Order-Book * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int} [limit] the maximum amount of order book entries to return * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async fetchOrderBookWs(symbol, limit = undefined, params = {}) { await this.loadMarkets(); const market = this.market(symbol); const payload = { 'symbol': market['id'], }; if (limit !== undefined) { payload['limit'] = limit; } const marketType = this.getMarketType('fetchOrderBookWs', market, params); if (marketType !== 'future') { throw new BadRequest(this.id + ' fetchOrderBookWs only supports swap markets'); } const url = this.urls['api']['ws']['ws-api'][marketType]; const requestId = this.requestId(url); const messageHash = requestId.toString(); let returnRateLimits = false; [returnRateLimits, params] = this.handleOptionAndParams(params, 'createOrderWs', 'returnRateLimits', false); payload['returnRateLimits'] = returnRateLimits; params = this.omit(params, 'test'); const message = { 'id': messageHash, 'method': 'depth', 'params': this.signParams(this.extend(payload, params)), }; const subscription = { 'method': this.handleFetchOrderBook, }; const orderbook = await this.watch(url, messageHash, message, messageHash, subscription); orderbook['symbol'] = market['symbol']; return orderbook; } handleFetchOrderBook(client, message) { // // { // "id":"51e2affb-0aba-4821-ba75-f2625006eb43", // "status":200, // "result":{ // "lastUpdateId":1027024, // "E":1589436922972, // "T":1589436922959, // "bids":[ // [ // "4.00000000", // "431.00000000" // ] // ], // "asks":[ // [ // "4.00000200", // "12.00000000" // ] // ] // } // } // const messageHash = this.safeString(message, 'id'); const result = this.safeDict(message, 'result'); const timestamp = this.safeInteger(result, 'T'); const orderbook = this.parseOrderBook(result, undefined, timestamp); orderbook['nonce'] = this.safeInteger2(result, 'lastUpdateId', 'u'); client.resolve(orderbook, messageHash); } async fetchOrderBookSnapshot(client, message, subscription) { const symbol = this.safeString(subscription, 'symbol'); const messageHash = 'orderbook::' + symbol; try { const defaultLimit = this.safeInteger(this.options, 'watchOrderBookLimit', 1000); const type = this.safeValue(subscription, 'type'); const limit = this.safeInteger(subscription, 'limit', defaultLimit); const params = this.safeValue(subscription, 'params'); // 3. Get a depth snapshot from https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000 . // todo: this is a synch blocking call - make it async // default 100, max 1000, valid limits 5, 10, 20, 50, 100, 500, 1000 const snapshot = await this.fetchRestOrderBookSafe(symbol, limit, params); if (this.safeValue(this.orderbooks, symbol) === undefined) { // if the orderbook is dropped before the snapshot is received return; } const orderbook = this.orderbooks[symbol]; orderbook.reset(snapshot); // unroll the accumulated deltas const messages = orderbook.cache; orderbook.cache = []; for (let i = 0; i < messages.length; i++) { const messageItem = messages[i]; const U = this.safeInteger(messageItem, 'U'); const u = this.safeInteger(messageItem, 'u'); const pu = this.safeInteger(messageItem, 'pu'); if (type === 'future') { // 4. Drop any event where u is < lastUpdateId in the snapshot if (u < orderbook['nonce']) { continue; } // 5. The first processed event should have U <= lastUpdateId AND u >= lastUpdateId if ((U <= orderbook['nonce']) && (u >= orderbook['nonce']) || (pu === orderbook['nonce'])) { this.handleOrderBookMessage(client, messageItem, orderbook); } } else { // 4. Drop any event where u is <= lastUpdateId in the snapshot if (u <= orderbook['nonce']) { continue; } // 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1 if (((U - 1) <= orderbook['nonce']) && ((u - 1) >= orderbook['nonce'])) { this.handleOrderBookMessage(client, messageItem, orderbook); } } } this.orderbooks[symbol] = orderbook; client.resolve(orderbook, messageHash); } catch (e) { delete client.subscriptions[messageHash]; client.reject(e, messageHash); } } handleDelta(bookside, delta) { const price = this.safeFloat(delta, 0); const amount = this.safeFloat(delta, 1); bookside.store(price, amount); } handleDeltas(bookside, deltas) { for (let i = 0; i < deltas.length; i++) { this.handleDelta(bookside, deltas[i]); } } handleOrderBookMessage(client, message, orderbook) { const u = this.safeInteger(message, 'u'); this.handleDeltas(orderbook['asks'], this.safeValue(message, 'a', [])); this.handleDeltas(orderbook['bids'], this.safeValue(message, 'b', [])); orderbook['nonce'] = u; const timestamp = this.safeInteger(message, 'E'); orderbook['timestamp'] = timestamp; orderbook['datetime'] = this.iso8601(timestamp); return orderbook; } handleOrderBook(client, message) { // // initial snapshot is fetched with ccxt's fetchOrderBook // the feed does not include a snapshot, just the deltas // // { // "e": "depthUpdate", // Event type // "E": 1577554482280, // Event time // "s": "BNBBTC", // Symbol // "U": 157, // First update ID in event // "u": 160, // Final update ID in event // "b": [ // bids // [ "0.0024", "10" ], // price, size // ], // "a": [ // asks // [ "0.0026", "100" ], // price, size // ] // } // const isSpot = (client.url.indexOf('/stream') > -1); const marketType = (isSpot) ? 'spot' : 'contract'; const marketId = this.safeString(message, 's'); const market = this.safeMarket(marketId, undefined, undefined, marketType); const symbol = market['symbol']; const messageHash = 'orderbook::' + symbol; if (!(symbol in this.orderbooks)) { // // https://github.com/ccxt/ccxt/issues/6672 // // Sometimes Binance sends the first delta before the subscription // confirmation arrives. At that point the orderbook is not // initialized yet and the snapshot has not been requested yet // therefore it is safe to drop these premature messages. // return; } const orderbook = this.orderbooks[symbol]; const nonce = this.safeInteger(orderbook, 'nonce'); if (nonce === undefined) { // 2. Buffer the events you receive from the stream. orderbook.cache.push(message); } else { try { const U = this.safeInteger(message, 'U'); const u = this.safeInteger(message, 'u'); const pu = this.safeInteger(message, 'pu'); if (pu === undefined) { // spot // 4. Drop any event where u is <= lastUpdateId in the snapshot if (u > orderbook['nonce']) { const timestamp = this.safeInteger(orderbook, 'timestamp'); let conditional = undefined; if (timestamp === undefined) { // 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1