UNPKG

@proton/ccxt

Version:

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

777 lines (774 loc) 33.6 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 btcexRest from '../btcex.js'; import { NotSupported, ExchangeError, ArgumentsRequired } from '../base/errors.js'; import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp } from '../base/ws/Cache.js'; // --------------------------------------------------------------------------- export default class btcex extends btcexRest { describe() { return this.deepExtend(super.describe(), { 'has': { 'ws': true, 'watchBalance': true, 'watchTicker': true, 'watchTickers': false, 'watchTrades': true, 'watchMyTrades': true, 'watchOrders': true, 'watchOrderBook': true, 'watchOHLCV': true, }, 'urls': { 'api': { 'ws': 'wss://api.btcex.com/ws/api/v1', }, }, 'options': { 'watchOrderBook': { 'snapshotDelay': 0, 'maxRetries': 3, }, }, 'streaming': { 'ping': this.ping, 'keepAlive': 5000, }, 'exceptions': {}, 'timeframes': { '1m': '1', '3m': '3', '5m': '4', '10m': '10', '15m': '15', '30m': '30', '1h': '60', '2h': '120', '3h': '180', '4h': '240', '6h': '360', '12h': '720', '1d': '1D', }, }); } requestId() { const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1); this.options['requestId'] = requestId; return requestId.toString(); } async watchBalance(params = {}) { /** * @method * @name btcex#watchBalance * @description query for balance and get the amount of funds available for trading or funds locked in orders * @see https://docs.btcex.com/#user-asset-asset_type * @param {object} params extra parameters specific to the btcex api endpoint * @param {string} params.type asset type WALLET, BTC,ETH,MARGIN,SPOT,PERPETUAL * @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure} */ const token = await this.authenticate(params); let type = undefined; [type, params] = this.handleMarketTypeAndParams('watchBalance', undefined, params); const types = this.safeValue(this.options, 'accountsByType', {}); const assetType = this.safeString(types, type, type); params = this.omit(params, 'type'); const messageHash = 'balancess'; const url = this.urls['api']['ws']; const subscribe = { 'jsonrpc': '2.0', 'id': this.requestId(), 'method': '/private/subscribe', 'params': { 'access_token': token, 'channels': [ 'user.asset.' + assetType, ], }, }; const request = this.deepExtend(subscribe, params); return await this.watch(url, messageHash, request, messageHash, request); } handleBalance(client, message) { // // { // "jsonrpc": "2.0", // "method": "subscription", // "params": { // "channel": "user.asset.WALLET", // "data": { // "WALLET": { // "total": "5578184962", // "coupon": "0", // "details": [ // { // "available": "4999", // "freeze": "0", // "coin_type": "BTC", // "current_mark_price": "38000" // }, // ... // ] // } // } // } // } // const params = this.safeValue(message, 'params', {}); const data = this.safeValue(params, 'data', {}); const messageHash = 'balancess'; this.balance = this.parseBalance(data); client.resolve(this.balance, messageHash); } async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name btcex#watchOHLCV * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market. * @see https://docs.btcex.com/#chart-trades-instrument_name-resolution * @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|undefined} since timestamp in ms of the earliest candle to fetch * @param {int|undefined} limit the maximum amount of candles to fetch * @param {object} params extra parameters specific to the bitfinex2 api endpoint * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume */ await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; let instrumentName = market['id']; if (market['spot']) { instrumentName = market['baseId'] + '-' + market['quoteId']; } const interval = this.safeString(this.timeframes, timeframe, timeframe); const messageHash = 'ohlcv:' + symbol + ':' + interval; let request = { 'jsonrpc': '2.0', 'id': this.requestId(), 'method': '/public/subscribe', 'params': { 'channels': [ 'chart.trades.' + instrumentName + '.' + interval, ], }, }; request = this.deepExtend(request, params); const url = this.urls['api']['ws']; const ohlcv = await this.watch(url, messageHash, request, messageHash, request); if (this.newUpdates) { limit = ohlcv.getLimit(symbol, limit); } return this.filterBySinceLimit(ohlcv, since, limit, 0, true); } handleOHLCV(client, message) { // // { // "params": { // "data": { // "tick": "1660095420", // "open": "22890.30000000", // "high": "22890.50000000", // "low": "22886.50000000", // "close": "22886.50000000", // "volume": "314.46800000", // "cost": "7197974.01690000" // }, // "channel": "chart.trades.BTC-USDT-PERPETUAL.1" // }, // "method": "subscription", // "jsonrpc": "2.0" // } // const params = this.safeValue(message, 'params'); const channel = this.safeString(params, 'channel'); const symbolInterval = channel.slice(13); const dotIndex = symbolInterval.indexOf('.'); const marketId = symbolInterval.slice(0, dotIndex); const timeframeId = symbolInterval.slice(dotIndex + 1); const timeframe = this.findTimeframe(timeframeId); const symbol = this.safeSymbol(marketId, undefined, '-'); const messageHash = 'ohlcv:' + symbol + ':' + timeframeId; const data = this.safeValue(params, 'data', {}); const ohlcv = this.parseOHLCV(data); 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 ArrayCacheByTimestamp(limit); this.ohlcvs[symbol][timeframe] = stored; } stored.append(ohlcv); client.resolve(stored, messageHash); } async watchTicker(symbol, params = {}) { /** * @method * @name btcex#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.btcex.com/#ticker-instrument_name-interval * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} params extra parameters specific to the btcex api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; let instrumentName = market['id']; if (market['spot']) { instrumentName = market['baseId'] + '-' + market['quoteId']; } const url = this.urls['api']['ws']; const messageHash = 'ticker:' + symbol; let request = { 'jsonrpc': '2.0', 'id': this.requestId(), 'method': '/public/subscribe', 'params': { 'channels': [ 'ticker.' + instrumentName + '.raw', ], }, }; request = this.deepExtend(request, params); return await this.watch(url, messageHash, request, messageHash); } handleTicker(client, message) { // // { // "params": { // "data": { // "timestamp": "1660094543813", // "stats": { // "volume": "630219.70300000000008822", // "price_change": "-0.0378", // "low": "22659.50000000", // "turnover": "14648416962.26930706016719341", // "high": "23919.00000000" // }, // "state": "open", // "last_price": "22890.00000000", // "instrument_name": "BTC-USDT-PERPETUAL", // "best_bid_price": "22888.60000000", // "best_bid_amount": "33.38500000", // "best_ask_price": "22889.40000000", // "best_ask_amount": "5.45200000", // "mark_price": "22890.5", // "underlying_price": "22891", // "open_interest": "33886.083" // }, // "channel": "ticker.BTC-USDT-PERPETUAL.raw" // }, // "method": "subscription", // "jsonrpc": "2.0" // } // const params = this.safeValue(message, 'params'); const data = this.safeValue(params, 'data'); const ticker = this.parseTicker(data); const symbol = this.safeString(ticker, 'symbol'); const messageHash = 'ticker:' + symbol; this.tickers[symbol] = ticker; client.resolve(ticker, messageHash); } async watchTrades(symbol, since = undefined, limit = undefined, params = {}) { /** * @method * @name btcex#watchTrades * @description get the list of most recent trades for a particular symbol * @see https://docs.btcex.com/#trades-instrument_name-interval * @param {string} symbol unified symbol of the market to fetch trades for * @param {int|undefined} since timestamp in ms of the earliest trade to fetch * @param {int|undefined} limit the maximum amount of trades to fetch * @param {object} params extra parameters specific to the btcex api endpoint * @returns {[object]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html?#public-trades} */ await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; const url = this.urls['api']['ws']; const messageHash = 'trades:' + symbol; let request = { 'jsonrpc': '2.0', 'id': this.requestId(), 'method': '/public/subscribe', 'params': { 'channels': [ 'trades.' + market['id'] + '.raw', ], }, }; request = this.deepExtend(request, params); const trades = await this.watch(url, messageHash, request, messageHash, request); if (this.newUpdates) { limit = trades.getLimit(symbol, limit); } return this.filterBySinceLimit(trades, since, limit, 'timestamp', true); } handleTrades(client, message) { // // { // "jsonrpc": "2.0", // "method": "subscription", // "params": { // "channel": "trades.BTC-USDT-PERPETUAL.raw", // "data": [{ // "timestamp": "1660093462553", // "price": "22815.9", // "amount": "4.479", // "iv": "0", // "direction": "sell", // "instrument_name": "BTC-USDT-PERPETUAL", // "trade_id": "227976617", // "mark_price": "22812.7" // }] // } // } // const params = this.safeValue(message, 'params', {}); const fullChannel = this.safeString(params, 'channel'); const parts = fullChannel.split('.'); const marketId = parts[1]; const symbol = this.safeSymbol(marketId); const messageHash = 'trades:' + symbol; let stored = this.safeValue(this.trades, symbol); if (stored === undefined) { const limit = this.safeInteger(this.options, 'tradesLimit', 1000); stored = new ArrayCache(limit); this.trades[symbol] = stored; } const rawTrades = this.safeValue(params, 'data', []); for (let i = 0; i < rawTrades.length; i++) { const rawTrade = rawTrades[i]; const trade = this.parseTrade(rawTrade, undefined); stored.append(trade); } this.trades[symbol] = stored; client.resolve(stored, messageHash); } async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name bibox#fetchMyTrades * @description watch all trades made by the user * @see https://docs.btcex.com/#user-trades-instrument_name-interval * @param {string} symbol unified market symbol * @param {int|undefined} since the earliest time in ms to fetch trades for * @param {int|undefined} limit the maximum number of trades structures to retrieve * @param {object} params extra parameters specific to the bibox api endpoint * @returns {[object]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure} */ if (symbol === undefined) { throw new ArgumentsRequired(this.id + ' watchMyTrades() requires a symbol argument'); } await this.loadMarkets(); const token = await this.authenticate(); const market = this.market(symbol); symbol = market['symbol']; const url = this.urls['api']['ws']; const messageHash = 'myTrades:' + symbol; const request = { 'jsonrpc': '2.0', 'id': this.requestId(), 'method': '/private/subscribe', 'params': { 'access_token': token, 'channels': [ 'user.trades.' + market['id'] + '.raw', ], }, }; 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); } handleMyTrades(client, message) { // // { // "jsonrpc": "2.0", // "method": "subscription", // "params": { // "channel": "user.trades.BTC-14AUG20.raw", // "data": [{ // "direction": "sell", // "amount": "1", // "price": "33000", // "iv": "0", // "fee": "0", // "timestamp": 1626148488157, // "trade_id": "1", // "order_id": "160717710099746816", // "instrument_name": "BTC-24SEP21", // "order_type": "limit", // "fee_coin_type": "USDT", // "index_price": "33157.63" // }] // } // } // const params = this.safeValue(message, 'params', {}); const channel = this.safeString(params, 'channel', ''); const endIndex = channel.indexOf('.raw'); const marketId = channel.slice(12, endIndex); const symbol = this.safeSymbol(marketId, undefined, '-'); const rawTrades = this.safeValue(params, 'data', []); let stored = this.myTrades; if (stored === undefined) { const limit = this.safeInteger(this.options, 'tradesLimit', 1000); stored = new ArrayCacheBySymbolById(limit); } for (let i = 0; i < rawTrades.length; i++) { const rawTrade = rawTrades[i]; const trade = this.parseTrade(rawTrade); stored.append(trade); } this.myTrades = stored; const messageHash = 'myTrades:' + symbol; client.resolve(stored, messageHash); } async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name btcex#fetchOrders * @description watches information on multiple orders made by the user * @see https://docs.btcex.com/#user-changes-kind-currency-interval * @param {string} symbol unified market symbol of the market orders were made in * @param {int|undefined} since the earliest time in ms to fetch orders for * @param {int|undefined} limit the maximum number of orde structures to retrieve * @param {object} params extra parameters specific to the btcex api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure} */ if (symbol === undefined) { throw new ArgumentsRequired(this.id + 'watchesOrders() requires a symbol'); } await this.loadMarkets(); const token = await this.authenticate(); const market = this.market(symbol); symbol = market['symbol']; const url = this.urls['api']['ws']; const message = { 'jsonrpc': '2.0', 'id': this.requestId(), 'method': '/private/subscribe', 'params': { 'access_token': token, 'channels': [ 'user.orders.' + market['id'] + '.raw', ], }, }; const messageHash = 'orders:' + symbol; const request = this.deepExtend(message, params); const orders = await this.watch(url, messageHash, request, messageHash); if (this.newUpdates) { limit = orders.getLimit(symbol, limit); } return this.filterBySymbolSinceLimit(orders, symbol, since, limit); } handleOrder(client, message) { // // { // "jsonrpc": "2.0", // "method": "subscription", // "params": { // "channel": "user.orders.BTC-14AUG20.raw", // "data": { // "amount": "1", // "price": "11895.00", // "direction": "buy", // "version": 0, // "order_state": "filled", // "instrument_name": "BTC-14AUG20", // "time_in_force": "good_til_cancelled", // "last_update_timestamp": 1597130534567, // "filled_amount": "1", // "average_price": "11770.00", // "order_id": "39007591615041536", // "creation_timestamp": 1597130534567, // "order_type": "limit" // } // } // const params = this.safeValue(message, 'params', {}); const rawOrder = this.safeValue(params, 'data', {}); let cachedOrders = this.orders; if (cachedOrders === undefined) { const limit = this.safeInteger(this.options, 'ordersLimit', 1000); cachedOrders = new ArrayCacheBySymbolById(limit); } const order = this.parseOrder(rawOrder); const symbol = this.safeString(order, 'symbol'); const messageHash = 'orders:' + symbol; cachedOrders.append(order); this.orders = cachedOrders; client.resolve(this.orders, messageHash); } async watchOrderBook(symbol, limit = undefined, params = {}) { /** * @method * @name btcex#watchOrderBook * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://docs.btcex.com/#book-instrument_name-interval * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int|undefined} limit the maximum amount of order book entries to return * @param {objectConstructor} params extra parameters specific to the btcex api endpoint * @param {string|undefined} params.type accepts l2 or l3 for level 2 or level 3 order book * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; let instrumentName = market['id']; if (market['spot']) { instrumentName = market['baseId'] + '-' + market['quoteId']; } const url = this.urls['api']['ws']; params = this.omit(params, 'type'); const messageHash = 'orderbook:' + symbol; const subscribe = { 'jsonrpc': '2.0', 'id': this.requestId(), 'method': '/public/subscribe', 'params': { 'channels': [ 'book.' + instrumentName + '.raw', ], }, }; const request = this.deepExtend(subscribe, params); const orderbook = await this.watch(url, messageHash, request, messageHash); return orderbook.limit(); } handleOrderBook(client, message) { // // { // "params": { // "data": { // "timestamp": 1626056933600, // "change_id": 1566764, // "asks": [ // [ // "new", // "34227.122", // "0.00554" // ], // ... // ], // "bids": [ // [ // "delete", // "34105.540", // "0" // ], // ... // ], // "instrument_name": "BTC-USDT" // }, // "channel": "book.BTC-USDT.raw" // }, // "method": "subscription", // "jsonrpc": "2.0" // } // ` const params = this.safeValue(message, 'params'); const data = this.safeValue(params, 'data'); const marketId = this.safeString(data, 'instrument_name'); const symbol = this.safeSymbol(marketId, undefined, '-'); const storedOrderBook = this.safeValue(this.orderbooks, symbol); const nonce = this.safeInteger(storedOrderBook, 'nonce'); const deltaNonce = this.safeInteger(data, 'change_id'); const messageHash = 'orderbook:' + symbol; if (nonce === undefined) { const cacheLength = storedOrderBook.cache.length; const snapshotDelay = this.handleOption('watchOrderBook', 'snapshotDelay', 0); if (cacheLength === snapshotDelay) { const limit = 0; this.spawn(this.loadOrderBook, client, messageHash, symbol, limit); } storedOrderBook.cache.push(data); return; } else if (deltaNonce <= nonce) { return; } this.handleDelta(storedOrderBook, data); client.resolve(storedOrderBook, messageHash); } getCacheIndex(orderBook, cache) { const firstElement = cache[0]; let lastChangeId = this.safeInteger(firstElement, 'change_id'); const nonce = this.safeInteger(orderBook, 'nonce'); if (nonce < lastChangeId - 1) { return -1; } for (let i = 0; i < cache.length; i++) { const delta = cache[i]; lastChangeId = this.safeInteger(delta, 'change_id'); if (nonce === lastChangeId - 1) { // nonce is inside the cache // [ d, d, n, d ] return i; } } return cache.length; } handleDelta(orderbook, delta) { const timestamp = this.safeInteger(delta, 'timestamp'); orderbook['timestamp'] = timestamp; orderbook['datetime'] = this.iso8601(timestamp); orderbook['nonce'] = this.safeInteger(delta, 'change_id'); const bids = this.safeValue(delta, 'bids', []); const asks = this.safeValue(delta, 'asks', []); const storedBids = orderbook['bids']; const storedAsks = orderbook['asks']; this.handleBidAsks(storedBids, bids); this.handleBidAsks(storedAsks, asks); } handleBidAsks(bookSide, bidAsks) { for (let i = 0; i < bidAsks.length; i++) { const bidAsk = this.parseBidAsk(bidAsks[i], 1, 2); bookSide.storeArray(bidAsk); } } handleUser(client, message) { const params = this.safeValue(message, 'params'); const fullChannel = this.safeString(params, 'channel'); const sliceUser = fullChannel.slice(5); const endIndex = sliceUser.indexOf('.'); const userChannel = sliceUser.slice(0, endIndex); const handlers = { 'asset': this.handleBalance, 'orders': this.handleOrder, 'trades': this.handleMyTrades, }; const handler = this.safeValue(handlers, userChannel); if (handler !== undefined) { return handler.call(this, client, message); } throw new NotSupported(this.id + ' received an unsupported message: ' + this.json(message)); } handleErrorMessage(client, message) { // // { // id: '1', // jsonrpc: '2.0', // usIn: 1660140064049, // usOut: 1660140064051, // usDiff: 2, // error: { code: 10000, message: 'Authentication Failure' } // } // const error = this.safeValue(message, 'error', {}); throw new ExchangeError(this.id + ' error: ' + this.json(error)); } handleAuthenticate(client, message) { // // { // id: '1', // jsonrpc: '2.0', // usIn: 1660140846671, // usOut: 1660140846688, // usDiff: 17, // result: { // access_token: 'xxxxxx43jIXYrF3VSm90ar+f5n447M3ll82AiFO58L85pxb/DbVf6Bn4ZyBX1i1tM/KYFBJ234ZkrUkwImUIEu8vY1PBh5JqaaaaaeGnao=', // token_type: 'bearer', // refresh_token: '/I56sUOB/zwpwo8X8Q0Z234bW8Lz1YNlXOXSP6C+ZJDWR+49CjVPr0Z3PVXoL3BOB234WxXtTid+YmNjQ8OqGn1MM9pQL5TKZ97s49SvaRc=', // expires_in: 604014, // scope: 'account:read_write block_trade:read_write trade:read_write wallet:read_write', // m: '00000000006e446c6b44694759735570786e5668387335431274546e633867474d647772717a463924a6d3746756951334b637459653970576d63693143e6e335972584e48594c74674c4d416872564a4d56424c347438737938736f4645747263315374454e73324e546d346e5651792b69696279336647347737413d3d' // } // } // const result = this.safeValue(message, 'result', {}); const expiresIn = this.safeInteger(result, 'expires_in', 0); this.options['expiresAt'] = this.sum(this.seconds(), expiresIn) * 1000; const accessToken = this.safeString(result, 'access_token'); client.resolve(accessToken, 'authenticated'); } handleSubscription(client, message) { const channels = this.safeValue(message, 'result', []); for (let i = 0; i < channels.length; i++) { const fullChannel = channels[i]; const parts = fullChannel.split('.'); const channel = this.safeString(parts, 0); const marketId = this.safeString(parts, 1); if (channel === 'book') { const symbol = this.safeSymbol(marketId, undefined, '-'); this.orderbooks[symbol] = this.orderBook({}); // get full depth book } } } handlePong(client, message) { client.lastPong = this.milliseconds(); } handleMessage(client, message) { if (message === 'PONG') { this.handlePong(client, message); return; } const error = this.safeValue(message, 'error'); if (error !== undefined) { return this.handleErrorMessage(client, message); } const result = this.safeValue(message, 'result', {}); const accessToken = this.safeString(result, 'access_token'); if (accessToken !== undefined) { return this.handleAuthenticate(client, message); } const method = this.safeString(message, 'method'); if (method === 'subscription') { const params = this.safeValue(message, 'params'); const fullChannel = this.safeString(params, 'channel'); const parts = fullChannel.split('.'); const channel = this.safeString(parts, 0); const handlers = { 'ticker': this.handleTicker, 'trades': this.handleTrades, 'chart': this.handleOHLCV, 'balances': this.handleBalance, 'trading': this.handleOrder, 'user': this.handleUser, 'book': this.handleOrderBook, }; const handler = this.safeValue(handlers, channel); if (handler !== undefined) { return handler.call(this, client, message); } } else if ('result' in message) { this.handleSubscription(client, message); } return message; } authenticate(params = {}) { const url = this.urls['api']['ws']; const client = this.client(url); const messageHash = 'authenticated'; const expiresAt = this.safeNumber(this.options, 'expiresAt'); const time = this.milliseconds(); let future = this.safeValue(client.subscriptions, messageHash); if ((future === undefined) || (expiresAt <= time)) { const request = { 'jsonrpc': '2.0', 'id': this.requestId(), 'method': '/public/auth', 'params': { 'grant_type': 'client_credentials', 'client_id': this.apiKey, 'client_secret': this.secret, }, }; const message = this.extend(request, params); future = this.watch(url, messageHash, message); client.subscriptions[messageHash] = future; } return future; } ping(client) { return 'PING'; } }