UNPKG

ccxt

Version:

A cryptocurrency trading API with more than 100 exchanges in JavaScript / TypeScript / Python / C# / PHP / Go

1,107 lines (1,104 loc) • 83.1 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var sha2_js = require('@noble/hashes/sha2.js'); var bitvavo$1 = require('../bitvavo.js'); var errors = require('../base/errors.js'); var Cache = require('../base/ws/Cache.js'); // ---------------------------------------------------------------------------- // --------------------------------------------------------------------------- class bitvavo extends bitvavo$1["default"] { describe() { return this.deepExtend(super.describe(), { 'has': { 'ws': true, 'cancelOrdersWs': false, 'fetchTradesWs': false, 'watchOrderBook': true, 'watchOrderBookForSymbols': true, 'watchTrades': true, 'watchTradesForSymbols': true, 'watchTicker': true, 'watchTickers': true, 'watchBidsAsks': true, 'watchOHLCV': true, 'watchOHLCVForSymbols': true, 'watchOrders': true, 'watchMyTrades': true, 'unWatchOrderBook': true, 'unWatchOrderBookForSymbols': true, 'unWatchTrades': true, 'unWatchTradesForSymbols': true, 'unWatchOHLCV': true, 'unWatchOHLCVForSymbols': true, 'cancelAllOrdersWs': true, 'cancelOrderWs': true, 'createOrderWs': true, 'createStopLimitOrderWs': true, 'createStopMarketOrderWs': true, 'createStopOrderWs': true, 'editOrderWs': true, 'fetchBalanceWs': true, 'fetchCurrenciesWS': true, 'fetchDepositAddressWs': false, 'fetchDepositsWs': true, 'fetchDepositWithdrawFeesWs': false, 'fetchMyTradesWs': true, 'fetchOHLCVWs': true, 'fetchOpenOrdersWs': true, 'fetchOrderWs': true, 'fetchOrderBookWs': false, 'fetchOrdersWs': true, 'fetchTickerWs': false, 'fetchTickersWs': false, 'fetchTimeWs': false, 'fetchTradingFeesWs': true, 'fetchWithdrawalsWs': true, 'withdrawWs': true, }, 'urls': { 'api': { 'ws': 'wss://ws.bitvavo.com/v2', }, }, 'options': { 'supressMultipleWsRequestsError': false, // if true, will not throw an error when using the same messageHash for more than one request. By making false you may receive responses from different requests on the same action 'tradesLimit': 1000, 'ordersLimit': 1000, 'OHLCVLimit': 1000, }, }); } async watchPublic(name, symbol, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const market = this.market(symbol); const messageHash = name + '@' + market['id']; const url = this.urls['api']['ws']; const request = { 'action': 'subscribe', 'channels': [ { 'name': name, 'markets': [ market['id'], ], }, ], }; const message = this.extend(request, params); return await this.watch(url, messageHash, message, messageHash); } async watchPublicMultiple(methodName, channelName, symbols, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } symbols = this.marketSymbols(symbols); const messageHashes = [methodName]; const args = []; for (let i = 0; i < symbols.length; i++) { const market = this.market(symbols[i]); args.push(market['id']); } const url = this.urls['api']['ws']; const request = { 'action': 'subscribe', 'channels': [ { 'name': channelName, 'markets': args, }, ], }; const message = this.extend(request, params); return await this.watchMultiple(url, messageHashes, message, messageHashes); } /** * @method * @name bitvavo#watchTicker * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @see https://docs.bitvavo.com/#tag/Market-data-subscription-WebSocket/paths/~1subscribeTicker24h/post * @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} */ watchTicker(symbol, params = {}) { return this.watchPublic('ticker24h', symbol, params); } /** * @method * @name bitvavo#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 * @see https://docs.bitvavo.com/#tag/Market-data-subscription-WebSocket/paths/~1subscribeTicker24h/post * @param {string[]} [symbols] 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 watchTickers(symbols = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } symbols = this.marketSymbols(symbols, undefined, false); const channel = 'ticker24h'; const tickers = await this.watchPublicMultiple(channel, channel, symbols, params); return this.filterByArray(tickers, 'symbol', symbols); } handleTicker(client, message) { // // { // "event": "ticker24h", // "data": [ // { // "market": "ETH-EUR", // "open": "193.5", // "high": "202.72", // "low": "192.46", // "last": "199.01", // "volume": "3587.05020246", // "volumeQuote": "708030.17", // "bid": "199.56", // "bidSize": "4.14730802", // "ask": "199.57", // "askSize": "6.13642074", // "timestamp": 1590770885217 // } // ] // } // this.handleBidAsk(client, message); const event = this.safeString(message, 'event'); const tickers = this.safeValue(message, 'data', []); const result = []; for (let i = 0; i < tickers.length; i++) { const data = tickers[i]; const marketId = this.safeString(data, 'market'); const market = this.safeMarket(marketId, undefined, '-'); const messageHash = event + '@' + marketId; const ticker = this.parseTicker(data, market); const symbol = ticker['symbol']; this.tickers[symbol] = ticker; result.push(ticker); client.resolve(ticker, messageHash); } client.resolve(result, event); } /** * @method * @name bitvavo#watchBidsAsks * @description watches best bid & ask for symbols * @see https://docs.bitvavo.com/#tag/Market-data-subscription-WebSocket/paths/~1subscribeTicker24h/post * @param {string[]} symbols 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 watchBidsAsks(symbols = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } symbols = this.marketSymbols(symbols, undefined, false); const channel = 'ticker24h'; const tickers = await this.watchPublicMultiple('bidask', channel, symbols, params); return this.filterByArray(tickers, 'symbol', symbols); } handleBidAsk(client, message) { const event = 'bidask'; const tickers = this.safeValue(message, 'data', []); const result = []; for (let i = 0; i < tickers.length; i++) { const data = tickers[i]; const ticker = this.parseWsBidAsk(data); const symbol = ticker['symbol']; this.bidsasks[symbol] = ticker; result.push(ticker); const messageHash = event + ':' + symbol; client.resolve(ticker, messageHash); } client.resolve(result, event); } parseWsBidAsk(ticker, market = undefined) { const marketId = this.safeString(ticker, 'market'); market = this.safeMarket(marketId, undefined, '-'); const symbol = this.safeString(market, 'symbol'); const timestamp = this.safeInteger(ticker, 'timestamp'); return this.safeTicker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'ask': this.safeNumber(ticker, 'ask'), 'askVolume': this.safeNumber(ticker, 'askSize'), 'bid': this.safeNumber(ticker, 'bid'), 'bidVolume': this.safeNumber(ticker, 'bidSize'), 'info': ticker, }, market); } /** * @method * @name bitvavo#watchTrades * @description get the list of most recent trades for a particular symbol * @param {string} symbol unified symbol of the market to fetch trades for * @param {int} [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 = {}) { if (this.markets === undefined) { await this.loadMarkets(); } symbol = this.symbol(symbol); const trades = await this.watchPublic('trades', symbol, params); if (this.newUpdates) { limit = trades.getLimit(symbol, limit); } return this.filterBySinceLimit(trades, since, limit, 'timestamp', true); } handleTrade(client, message) { // // { // "event": "trade", // "timestamp": 1590779594547, // "market": "ETH-EUR", // "id": "450c3298-f082-4461-9e2c-a0262cc7cc2e", // "amount": "0.05026233", // "price": "198.46", // "side": "buy" // } // const marketId = this.safeString(message, 'market'); const market = this.safeMarket(marketId, undefined, '-'); const symbol = market['symbol']; const name = 'trades'; const messageHash = name + '@' + marketId; const trade = this.parseTrade(message, market); let tradesArray = this.safeValue(this.trades, symbol); if (tradesArray === undefined) { const limit = this.safeInteger(this.options, 'tradesLimit', 1000); tradesArray = new Cache.ArrayCache(limit); } tradesArray.append(trade); this.trades[symbol] = tradesArray; client.resolve(tradesArray, messageHash); } /** * @method * @name bitvavo#watchTradesForSymbols * @description get the list of most recent trades for a list of symbols * @see https://docs.bitvavo.com/docs/websocket-api/trades-subscription/ * @param {string[]} symbols unified symbols of the markets 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 watchTradesForSymbols(symbols, since = undefined, limit = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } symbols = this.marketSymbols(symbols, undefined, false); const name = 'trades'; const marketIds = []; const messageHashes = []; for (let i = 0; i < symbols.length; i++) { const market = this.market(symbols[i]); marketIds.push(market['id']); messageHashes.push(name + '@' + market['id']); } const url = this.urls['api']['ws']; const request = { 'action': 'subscribe', 'channels': [ { 'name': name, 'markets': marketIds, }, ], }; const message = this.extend(request, params); const trades = await this.watchMultiple(url, messageHashes, message, messageHashes); if (this.newUpdates) { const first = this.safeValue(trades, 0); const tradeSymbol = this.safeString(first, 'symbol'); limit = trades.getLimit(tradeSymbol, limit); } return this.filterBySinceLimit(trades, since, limit, 'timestamp', true); } /** * @method * @name bitvavo#unWatchTrades * @description stop watching the list of most recent trades for a particular symbol * @see https://docs.bitvavo.com/docs/websocket-api/trades-subscription/ * @param {string} symbol unified symbol of the market to stop watching the trades for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {any} status of the unwatch request */ async unWatchTrades(symbol, params = {}) { return await this.unWatchTradesForSymbols([symbol], params); } /** * @method * @name bitvavo#unWatchTradesForSymbols * @description stop watching the list of most recent trades for a list of symbols * @see https://docs.bitvavo.com/docs/websocket-api/trades-subscription/ * @param {string[]} symbols unified symbols of the markets to stop watching the trades for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {any} status of the unwatch request */ async unWatchTradesForSymbols(symbols, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } symbols = this.marketSymbols(symbols, undefined, false); const name = 'trades'; const marketIds = []; const subMessageHashes = []; for (let i = 0; i < symbols.length; i++) { const market = this.market(symbols[i]); marketIds.push(market['id']); subMessageHashes.push(name + '@' + market['id']); } const channels = [ { 'name': name, 'markets': marketIds, }, ]; const subscriptionArgs = { 'symbols': symbols, }; return await this.unWatchChannels('trades', channels, subMessageHashes, subscriptionArgs, params); } /** * @method * @name bitvavo#watchOHLCV * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @param {string} symbol unified symbol of the market to fetch OHLCV data for * @param {string} timeframe the length of time each candle represents * @param {int} [since] timestamp in ms of the earliest candle to fetch * @param {int} [limit] the maximum amount of candles to fetch * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume */ async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const market = this.market(symbol); symbol = market['symbol']; const name = 'candles'; const marketId = market['id']; const interval = this.safeString(this.timeframes, timeframe, timeframe); const messageHash = name + '@' + marketId + '_' + interval; const url = this.urls['api']['ws']; const request = { 'action': 'subscribe', 'channels': [ { 'name': 'candles', 'interval': [interval], 'markets': [marketId], }, ], }; const message = this.extend(request, params); const ohlcv = await this.watch(url, messageHash, message, messageHash); if (this.newUpdates) { limit = ohlcv.getLimit(symbol, limit); } return this.filterBySinceLimit(ohlcv, since, limit, 0, true); } handleFetchOHLCV(client, message) { // // { // action: 'getCandles', // response: [ // [1690325820000, '26453', '26453', '26436', '26447', '0.01626246'], // [1690325760000, '26454', '26454', '26453', '26453', '0.00037707'] // ] // } // const response = this.safeValue(message, 'response'); const ohlcv = this.parseOHLCVs(response, undefined, undefined, undefined); const messageHash = this.safeString(message, 'requestId'); client.resolve(ohlcv, messageHash); } handleOHLCV(client, message) { // // { // "event": "candle", // "market": "BTC-EUR", // "interval": "1m", // "candle": [ // [ // 1590797160000, // "8480.9", // "8480.9", // "8480.9", // "8480.9", // "0.01038628" // ] // ] // } // const name = 'candles'; const marketId = this.safeString(message, 'market'); const market = this.safeMarket(marketId, undefined, '-'); const symbol = market['symbol']; const interval = this.safeString(message, 'interval'); // use a reverse lookup in a static map instead const timeframe = this.findTimeframe(interval); const messageHash = name + '@' + marketId + '_' + interval; const candles = this.safeValue(message, 'candle'); this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {}); let stored = this.safeValue(this.ohlcvs[symbol], timeframe); if (stored === undefined) { const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000); stored = new Cache.ArrayCacheByTimestamp(limit); this.ohlcvs[symbol][timeframe] = stored; } for (let i = 0; i < candles.length; i++) { const candle = candles[i]; const parsed = this.parseOHLCV(candle, market); stored.append(parsed); } client.resolve(stored, messageHash); // watchOHLCVForSymbols needs the symbol and timeframe to assemble its result client.resolve([symbol, timeframe, stored], 'multi:' + messageHash); } /** * @method * @name bitvavo#watchOHLCVForSymbols * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of multiple markets * @see https://docs.bitvavo.com/docs/websocket-api/candles-subscription/ * @param {string[][]} symbolsAndTimeframes array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/EUR', '1m'], ['ETH/EUR', '5m']] * @param {int} [since] timestamp in ms of the earliest candle to fetch * @param {int} [limit] the maximum amount of candles to fetch * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a dictionary of [symbol, timeframe] keyed arrays of candles ordered as timestamp, open, high, low, close, volume */ async watchOHLCVForSymbols(symbolsAndTimeframes, since = undefined, limit = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const name = 'candles'; const messageHashes = []; const marketIdsByInterval = {}; for (let i = 0; i < symbolsAndTimeframes.length; i++) { const symbolAndTimeframe = symbolsAndTimeframes[i]; const market = this.market(symbolAndTimeframe[0]); const timeframeString = symbolAndTimeframe[1]; const interval = this.safeString(this.timeframes, timeframeString, timeframeString); if (!(interval in marketIdsByInterval)) { marketIdsByInterval[interval] = []; } const intervalIds = marketIdsByInterval[interval]; intervalIds.push(market['id']); messageHashes.push('multi:' + name + '@' + market['id'] + '_' + interval); } const channels = []; const intervals = Object.keys(marketIdsByInterval); for (let i = 0; i < intervals.length; i++) { const interval = intervals[i]; channels.push({ 'name': name, 'interval': [interval], 'markets': marketIdsByInterval[interval], }); } const url = this.urls['api']['ws']; const request = { 'action': 'subscribe', 'channels': channels, }; const message = this.extend(request, params); const [symbol, timeframe, candles] = await this.watchMultiple(url, messageHashes, message, messageHashes); if (this.newUpdates) { limit = candles.getLimit(symbol, limit); } const filtered = this.filterBySinceLimit(candles, since, limit, 0, true); return this.createOHLCVObject(symbol, timeframe, filtered); } /** * @method * @name bitvavo#unWatchOHLCV * @description stop watching historical candlestick data for a market * @see https://docs.bitvavo.com/docs/websocket-api/candles-subscription/ * @param {string} symbol unified symbol of the market to stop watching the candles for * @param {string} timeframe the length of time each candle represents * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {any} status of the unwatch request */ async unWatchOHLCV(symbol, timeframe = '1m', params = {}) { return await this.unWatchOHLCVForSymbols([[symbol, timeframe]], params); } /** * @method * @name bitvavo#unWatchOHLCVForSymbols * @description stop watching historical candlestick data for multiple markets * @see https://docs.bitvavo.com/docs/websocket-api/candles-subscription/ * @param {string[][]} symbolsAndTimeframes array of arrays containing unified symbols and timeframes to stop watching the candles for, example [['BTC/EUR', '1m'], ['ETH/EUR', '5m']] * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {any} status of the unwatch request */ async unWatchOHLCVForSymbols(symbolsAndTimeframes, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const name = 'candles'; const subMessageHashes = []; const marketIdsByInterval = {}; for (let i = 0; i < symbolsAndTimeframes.length; i++) { const symbolAndTimeframe = symbolsAndTimeframes[i]; const market = this.market(symbolAndTimeframe[0]); const timeframeString = symbolAndTimeframe[1]; const interval = this.safeString(this.timeframes, timeframeString, timeframeString); if (!(interval in marketIdsByInterval)) { marketIdsByInterval[interval] = []; } const intervalIds = marketIdsByInterval[interval]; intervalIds.push(market['id']); // both the single-symbol and the multi-symbol watch hashes must be released subMessageHashes.push(name + '@' + market['id'] + '_' + interval); subMessageHashes.push('multi:' + name + '@' + market['id'] + '_' + interval); } const channels = []; const intervals = Object.keys(marketIdsByInterval); for (let i = 0; i < intervals.length; i++) { const interval = intervals[i]; channels.push({ 'name': name, 'interval': [interval], 'markets': marketIdsByInterval[interval], }); } const subscriptionArgs = { 'symbolsAndTimeframes': symbolsAndTimeframes, }; return await this.unWatchChannels('ohlcv', channels, subMessageHashes, subscriptionArgs, params); } /** * @method * @name bitvavo#watchOrderBook * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int} [limit] the maximum amount of order book entries to return * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} an [order book structure]{@link https://docs.ccxt.com/?id=order-book-structure} */ async watchOrderBook(symbol, limit = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const market = this.market(symbol); symbol = market['symbol']; const name = 'book'; const messageHash = name + '@' + market['id']; const url = this.urls['api']['ws']; const request = { 'action': 'subscribe', 'channels': [ { 'name': name, 'markets': [ market['id'], ], }, ], }; const subscription = { 'messageHash': messageHash, 'name': name, 'symbol': symbol, 'marketId': market['id'], 'method': this.handleOrderBookSubscription, 'limit': limit, 'params': params, }; const message = this.extend(request, params); const orderbook = await this.watch(url, messageHash, message, messageHash, subscription); return orderbook.limit(); } /** * @method * @name bitvavo#watchOrderBookForSymbols * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data for multiple markets * @see https://docs.bitvavo.com/docs/websocket-api/book-subscription/ * @param {string[]} symbols unified symbols of the markets 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} an [order book structure]{@link https://docs.ccxt.com/?id=order-book-structure} */ async watchOrderBookForSymbols(symbols, limit = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } symbols = this.marketSymbols(symbols, undefined, false); const name = 'book'; const marketIds = []; const messageHashes = []; for (let i = 0; i < symbols.length; i++) { const market = this.market(symbols[i]); marketIds.push(market['id']); messageHashes.push(name + '@' + market['id']); } const url = this.urls['api']['ws']; const request = { 'action': 'subscribe', 'channels': [ { 'name': name, 'markets': marketIds, }, ], }; // the per-market snapshot machinery reads the marketId from the buffered // delta messages, so the shared subscription only carries the common fields const subscription = { 'name': name, 'symbols': symbols, 'limit': limit, 'params': params, }; const message = this.extend(request, params); const orderbook = await this.watchMultiple(url, messageHashes, message, messageHashes, subscription); return orderbook.limit(); } /** * @method * @name bitvavo#unWatchOrderBook * @description stop watching the order book for a particular symbol * @see https://docs.bitvavo.com/docs/websocket-api/book-subscription/ * @param {string} symbol unified symbol of the market to stop watching the order book for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {any} status of the unwatch request */ async unWatchOrderBook(symbol, params = {}) { return await this.unWatchOrderBookForSymbols([symbol], params); } /** * @method * @name bitvavo#unWatchOrderBookForSymbols * @description stop watching the order book for multiple markets * @see https://docs.bitvavo.com/docs/websocket-api/book-subscription/ * @param {string[]} symbols unified symbols of the markets to stop watching the order book for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {any} status of the unwatch request */ async unWatchOrderBookForSymbols(symbols, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } symbols = this.marketSymbols(symbols, undefined, false); const name = 'book'; const marketIds = []; const subMessageHashes = []; for (let i = 0; i < symbols.length; i++) { const market = this.market(symbols[i]); marketIds.push(market['id']); subMessageHashes.push(name + '@' + market['id']); } const channels = [ { 'name': name, 'markets': marketIds, }, ]; const subscriptionArgs = { 'symbols': symbols, }; return await this.unWatchChannels('orderbook', channels, subMessageHashes, subscriptionArgs, params); } 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) { // // { // "event": "book", // "market": "BTC-EUR", // "nonce": 36947383, // "bids": [ // [ "8477.8", "0" ] // ], // "asks": [ // [ "8550.9", "0" ] // ] // } // const nonce = this.safeInteger(message, 'nonce'); if (nonce > orderbook['nonce']) { this.handleDeltas(orderbook['asks'], this.safeValue(message, 'asks', [])); this.handleDeltas(orderbook['bids'], this.safeValue(message, 'bids', [])); orderbook['nonce'] = nonce; } return orderbook; } handleOrderBook(client, message) { // // { // "event": "book", // "market": "BTC-EUR", // "nonce": 36729561, // "bids": [ // [ "8513.3", "0" ], // [ '8518.8', "0.64236203" ], // [ '8513.6', "0.32435481" ], // ], // "asks": [] // } // const event = this.safeString(message, 'event'); const marketId = this.safeString(message, 'market'); const market = this.safeMarket(marketId, undefined, '-'); const symbol = market['symbol']; const messageHash = event + '@' + market['id']; const orderbook = this.safeValue(this.orderbooks, symbol); if (orderbook === undefined) { return; } if (orderbook['nonce'] === undefined) { const subscription = this.safeValue(client.subscriptions, messageHash, {}); // multi-symbol watches share one subscription object, so the // snapshot-in-flight flag must be tracked per market const flagKey = 'watchingOrderBookSnapshot@' + marketId; const watchingOrderBookSnapshot = this.safeValue(subscription, flagKey); if (watchingOrderBookSnapshot === undefined) { subscription[flagKey] = true; client.subscriptions[messageHash] = subscription; const options = this.safeValue(this.options, 'watchOrderBookSnapshot', {}); const delay = this.safeInteger(options, 'delay', this.rateLimit); // fetch the snapshot in a separate async call after a warmup delay this.delay(delay, this.watchOrderBookSnapshot, client, message, subscription); } orderbook.cache.push(message); } else { this.handleOrderBookMessage(client, message, orderbook); client.resolve(orderbook, messageHash); } } async watchOrderBookSnapshot(client, message, subscription) { const params = this.safeValue(subscription, 'params'); // multi-symbol watches share one subscription object without a marketId, // in that case the buffered delta message identifies the market const marketId = this.safeString2(subscription, 'marketId', 'market', this.safeString(message, 'market')); const snapshotSymbol = this.safeSymbol(marketId, undefined, '-'); if (!(snapshotSymbol in this.orderbooks)) { // this snapshot fetch was scheduled before an unsubscribe removed the // order book - skip it so the getBook request is not sent for a dead market return undefined; } const name = 'getBook'; const messageHash = name + '@' + marketId; const url = this.urls['api']['ws']; const request = { 'action': name, 'market': marketId, }; const orderbook = await this.watch(url, messageHash, this.extend(request, params), messageHash, subscription); return orderbook.limit(); } handleOrderBookSnapshot(client, message) { // // { // "action": "getBook", // "response": { // "market": "BTC-EUR", // "nonce": 36946120, // "bids": [ // [ '8494.9', "0.24399521" ], // [ '8494.8', "0.34884085" ], // [ '8493.9', "0.14535128" ], // ], // "asks": [ // [ "8495", "0.46982463" ], // [ '8495.1', "0.12178267" ], // [ '8496.2', "0.21924143" ], // ] // } // } // const response = this.safeValue(message, 'response'); if (response === undefined) { return; } const marketId = this.safeString(response, 'market'); const symbol = this.safeSymbol(marketId, undefined, '-'); const name = 'book'; const messageHash = name + '@' + marketId; const orderbook = this.safeValue(this.orderbooks, symbol); if (orderbook === undefined) { // the market was unsubscribed while this snapshot request was in flight return; } const snapshot = this.parseOrderBook(response, symbol); snapshot['nonce'] = this.safeInteger(response, 'nonce'); orderbook.reset(snapshot); // unroll the accumulated deltas const messages = orderbook.cache; for (let i = 0; i < messages.length; i++) { const messageItem = messages[i]; this.handleOrderBookMessage(client, messageItem, orderbook); } this.orderbooks[symbol] = orderbook; client.resolve(orderbook, messageHash); // getBook is a one-shot request but this.watch tracks it as a persistent // subscription - drop it so a later unsubscribe/subscribe re-fetches the snapshot // instead of suppressing the request as an already-active subscription const snapshotHash = 'getBook@' + marketId; if (snapshotHash in client.subscriptions) { delete client.subscriptions[snapshotHash]; } } handleOrderBookSubscription(client, message, subscription) { const symbol = this.safeString(subscription, 'symbol'); const limit = this.safeInteger(subscription, 'limit'); if (symbol in this.orderbooks) { delete this.orderbooks[symbol]; } this.orderbooks[symbol] = this.orderBook({}, limit); } handleOrderBookSubscriptions(client, message, marketIds) { const name = 'book'; for (let i = 0; i < marketIds.length; i++) { const marketId = this.safeString(marketIds, i); const symbol = this.safeSymbol(marketId, undefined, '-'); const messageHash = name + '@' + marketId; if (!(symbol in this.orderbooks)) { const subscription = this.safeValue(client.subscriptions, messageHash); const method = this.safeValue(subscription, 'method'); if (method !== undefined) { method.call(this, client, message, subscription); } else if (subscription !== undefined) { // multi-symbol watches share one subscription object without a // per-market method - initialize the order book directly const limit = this.safeInteger(subscription, 'limit'); this.orderbooks[symbol] = this.orderBook({}, limit); } } } } async unWatchChannels(topic, channels, subMessageHashes, subscriptionArgs, params = {}) { const url = this.urls['api']['ws']; const request = { 'action': 'unsubscribe', 'channels': channels, }; const unsubHashes = []; for (let i = 0; i < subMessageHashes.length; i++) { unsubHashes.push('unsubscribe:' + subMessageHashes[i]); } const subscription = this.extend({ 'topic': topic, 'subMessageHashes': subMessageHashes, 'unsubHashes': unsubHashes, }, subscriptionArgs); const message = this.extend(request, params); return await this.watchMultiple(url, unsubHashes, message, unsubHashes, subscription); } handleUnsubscriptionStatus(client, message) { // // { // "event": "unsubscribed", // "subscriptions": {} // } // // the confirmation carries the remaining subscriptions without identifying // which unsubscribe request it belongs to, so settle every pending unsubscription const keys = Object.keys(client.subscriptions); for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (!(key in client.subscriptions)) { continue; } if (!key.startsWith('unsubscribe:')) { continue; } const subscription = client.subscriptions[key]; const subHash = key.replace('unsubscribe:', ''); this.cleanCache(subscription); this.cleanUnsubscription(client, subHash, key); // bitvavo resolves-and-deletes the data futures on every message, so at // unsubscribe time the sub future is usually already gone and cleanUnsubscription // stashes the error in client.rejections instead - that stale entry // would immediately reject the next subscribe's fresh future, so clear it here if (subHash in client.rejections) { delete client.rejections[subHash]; } } return message; } /** * @method * @name bitvavo#watchOrders * @description watches information on multiple orders made by the user * @param {string} symbol 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 * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/?id=order-structure} */ async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new errors.ArgumentsRequired(this.id + ' watchOrders() requires a symbol argument'); } if (this.markets === undefined) { await this.loadMarkets(); } await this.authenticate(); const market = this.market(symbol); symbol = market['symbol']; const marketId = market['id']; const url = this.urls['api']['ws']; const name = 'account'; const messageHash = 'order:' + symbol; const request = { 'action': 'subscribe', 'channels': [ { 'name': name, 'markets': [marketId], }, ], }; const orders = await this.watch(url, messageHash, request, messageHash); if (this.newUpdates) { limit = orders.getLimit(symbol, limit); } return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true); } /** * @method * @name bitvavo#watchMyTrades * @description watches information on multiple trades made by the user * @param {string} symbol unified market symbol of the market trades were made in * @param {int} [since] the earliest time in ms to fetch trades for * @param {int} [limit] the maximum number of trade structures to retrieve * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=trade-structure} */ async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new errors.ArgumentsRequired(this.id + ' watchMyTrades() requires a symbol argument'); } if (this.markets === undefined) { await this.loadMarkets(); } await this.authenticate(); const market = this.market(symbol); symbol = market['symbol']; const marketId = market['id']; const url = this.urls['api']['ws']; const name = 'account'; const messageHash = 'myTrades:' + symbol; const request = { 'action': 'subscribe', 'channels': [ { 'name': name, 'markets': [marketId], }, ], }; const trades = await this.watch(url, messageHash, request, messageHash); if (this.newUpdates) { limit = trades.getLimit(symbol, limit); } return this.filterBySymbolSinceLimit(trades, symbol, since, limit, true); } /** * @method * @name bitvavo#createOrderWs * @description create a trade order * @see https://docs.bitvavo.com/#tag/Orders/paths/~1order/post * @param {string} symbol unified symbol of the market to create an order in * @param {string} type 'market' or 'limit' * @param {string} side 'buy' or 'sell' * @param {float} amount how much of currency you want to trade in units of base currency * @param {float} price the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {string} [params.timeInForce] "GTC", "IOC", or "PO" * @param {float} [params.stopPrice] The price at which a trigger order is triggered at * @param {float} [params.triggerPrice] The price at which a trigger order is triggered at * @param {bool} [params.postOnly] If true, the order will only be posted to the order book and not executed immediately * @param {float} [params.stopLossPrice] The price at which a stop loss order is triggered at * @param {float} [params.takeProfitPrice] The price at which a take profit order is triggered at * @param {string} [params.triggerType] "price" * @param {string} [params.triggerReference] "lastTrade", "bestBid", "bestAsk", "midPrice" Only for stop orders: Use this to determine which parameter will trigger the order * @param {string} [params.selfTradePrevention] "decrementAndCancel", "cancelOldest", "cancelNewest", "cancelBoth" * @param {bool} [params.disableMarketProtection] don't cancel if the next fill price is 10% worse than the best fill price * @param {bool} [params.responseRequired] Set this to 'false' when only an acknowledgement of success or failure is required, this is faster. * @returns {object} an [order structure]{@link https://docs.ccxt.com/?id=order-structure} */ async createOrderWs(symbol, type, side, amount, price = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } await this.authenticate(); const request = this.createOrderRequest(symbol, type, side, amount, price, params); return await this.watchRequest('privateCreateOrder', request); } /** * @method * @name bitvavo#editOrderWs * @description edit a trade order * @see https://docs.bitvavo.com/#tag/Orders/paths/~1order/put * @param {string} id cancel order id * @param {string} symbol unified symbol of the market to create an order in * @param {string} type 'market' or 'limit' * @param {string} side 'buy' or 'sell' * @param {float} [amount] how much of currency you want to trade in units of base currency * @param {float} [price] the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} an [order structure]{@link https://docs.ccxt.com/?id=order-structure} */ async editOrderWs(id, symbol, type, side, amount = undefined, price = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } await this.authenticate(); const request = this.editOrderRequest(id, symbol, type, side, amount, price, params); return await this.watchRequest('privateUpdateOrder', request); } /** * @method * @name bitvavo#cancelOrderWs * @see https://docs.bitvavo.com/#tag/Orders/paths/~1order/delete * @description cancels an open order * @param {string} id order id * @param {string} symbol unified symbol of the market the order was made in * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} An [order structure]{@link https://docs.ccxt.com/?id=order-structure} */ async cancelOrderWs(id, symbol = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } await this.authenticate(); const request = this.cancelOrderRequest(id, symbol, params); return await this.watchRequest('privateCancelOrder', request); } /** * @method * @name bitvavo#cancelAllOrdersWs * @see https://docs.bitvavo.com/#tag/Orders/paths/~1orders/delete * @description cancel all open orders * @param {string} symbol unified market symbol, only orders in the market of this symbol are cancelled when symbol is not undefined * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/?id=order-structure} */ async cancelAllOrdersWs(symbol = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } await this.authenticate(); const request = {}; let operatorId = undefined; [operatorId, params] = this.handleOptionAndParams(params, 'cancelAllOrdersWs', 'op