UNPKG

@proton/ccxt

Version:

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

599 lines (596 loc) 23.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 hollaexRest from '../hollaex.js'; import { AuthenticationError, BadSymbol, BadRequest } from '../base/errors.js'; import { ArrayCache, ArrayCacheBySymbolById } from '../base/ws/Cache.js'; import { sha256 } from '../static_dependencies/noble-hashes/sha256.js'; // --------------------------------------------------------------------------- export default class hollaex extends hollaexRest { describe() { return this.deepExtend(super.describe(), { 'has': { 'ws': true, 'watchBalance': true, 'watchMyTrades': false, 'watchOHLCV': false, 'watchOrderBook': true, 'watchOrders': true, 'watchTicker': false, 'watchTickers': false, 'watchTrades': true, }, 'urls': { 'api': { 'ws': 'wss://api.hollaex.com/stream', }, 'test': { 'ws': 'wss://api.sandbox.hollaex.com/stream', }, }, 'options': { 'watchBalance': { // 'api-expires': undefined, }, 'watchOrders': { // 'api-expires': undefined, }, }, 'streaming': { 'ping': this.ping, }, 'exceptions': { 'ws': { 'exact': { 'Bearer or HMAC authentication required': BadSymbol, 'Error: wrong input': BadRequest, // { error: 'Error: wrong input' } }, }, }, }); } async watchOrderBook(symbol, limit = undefined, params = {}) { /** * @method * @name hollaex#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|undefined} limit the maximum amount of order book entries to return * @param {object} params extra parameters specific to the hollaex api endpoint * @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); const messageHash = 'orderbook' + ':' + market['id']; const orderbook = await this.watchPublic(messageHash, params); return orderbook.limit(); } handleOrderBook(client, message) { // // { // "topic":"orderbook", // "action":"partial", // "symbol":"ltc-usdt", // "data":{ // "bids":[ // [104.29, 5.2264], // [103.86,1.3629], // [101.82,0.5942] // ], // "asks":[ // [104.81,9.5531], // [105.54,0.6416], // [106.18,1.4141], // ], // "timestamp":"2022-04-12T08:17:05.932Z" // }, // "time":1649751425 // } // const marketId = this.safeString(message, 'symbol'); const channel = this.safeString(message, 'topic'); const market = this.safeMarket(marketId); const symbol = market['symbol']; const data = this.safeValue(message, 'data'); const timestamp = this.safeString(data, 'timestamp'); const timestampMs = this.parse8601(timestamp); const snapshot = this.parseOrderBook(data, symbol, timestampMs); let orderbook = undefined; if (!(symbol in this.orderbooks)) { orderbook = this.orderBook(snapshot); this.orderbooks[symbol] = orderbook; } else { orderbook = this.orderbooks[symbol]; orderbook.reset(snapshot); } const messageHash = channel + ':' + marketId; client.resolve(orderbook, messageHash); } async watchTrades(symbol, since = undefined, limit = undefined, params = {}) { /** * @method * @name hollaex#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|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 hollaex 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 messageHash = 'trade' + ':' + market['id']; const trades = await this.watchPublic(messageHash, params); if (this.newUpdates) { limit = trades.getLimit(symbol, limit); } return this.filterBySinceLimit(trades, since, limit, 'timestamp', true); } handleTrades(client, message) { // // { // topic: 'trade', // action: 'partial', // symbol: 'btc-usdt', // data: [ // { // size: 0.05145, // price: 41977.9, // side: 'buy', // timestamp: '2022-04-11T09:40:10.881Z' // }, // ] // } // const channel = this.safeString(message, 'topic'); const marketId = this.safeString(message, 'symbol'); const market = this.safeMarket(marketId); const symbol = market['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 data = this.safeValue(message, 'data', []); const parsedTrades = this.parseTrades(data, market); for (let j = 0; j < parsedTrades.length; j++) { stored.append(parsedTrades[j]); } const messageHash = channel + ':' + marketId; client.resolve(stored, messageHash); client.resolve(stored, channel); } async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name hollaex#watchMyTrades * @description watches information on multiple trades made by the user * @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 hollaex api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure */ await this.loadMarkets(); let messageHash = 'usertrade'; let market = undefined; if (symbol !== undefined) { market = this.market(symbol); symbol = market['symbol']; messageHash += ':' + market['id']; } const trades = await this.watchPrivate(messageHash, params); if (this.newUpdates) { limit = trades.getLimit(symbol, limit); } return this.filterBySymbolSinceLimit(trades, symbol, since, limit, true); } handleMyTrades(client, message, subscription = undefined) { // // { // "topic":"usertrade", // "action":"insert", // "user_id":"103", // "symbol":"xht-usdt", // "data":[ // { // "size":1, // "side":"buy", // "price":0.24, // "symbol":"xht-usdt", // "timestamp":"2022-05-13T09:30:15.014Z", // "order_id":"6065a66e-e9a4-44a3-9726-4f8fa54b6bb6", // "fee":0.001, // "fee_coin":"xht", // "is_same":true // } // ], // "time":1652434215 // } // const channel = this.safeString(message, 'topic'); const rawTrades = this.safeValue(message, 'data'); // usually the first message is an empty array // when the user does not have any trades yet const dataLength = rawTrades.length; if (dataLength === 0) { return 0; } if (this.myTrades === undefined) { const limit = this.safeInteger(this.options, 'tradesLimit', 1000); this.myTrades = new ArrayCache(limit); } const stored = this.myTrades; const marketIds = {}; for (let i = 0; i < rawTrades.length; i++) { const trade = rawTrades[i]; const parsed = this.parseTrade(trade); stored.append(parsed); const symbol = trade['symbol']; const market = this.market(symbol); const marketId = market['id']; marketIds[marketId] = true; } // non-symbol specific client.resolve(this.myTrades, channel); const keys = Object.keys(marketIds); for (let i = 0; i < keys.length; i++) { const marketId = keys[i]; const messageHash = channel + ':' + marketId; client.resolve(this.myTrades, messageHash); } } async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name hollaex#watchOrders * @description watches information on multiple orders made by the user * @param {string|undefined} 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 hollaex api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure} */ await this.loadMarkets(); let messageHash = 'order'; let market = undefined; if (symbol !== undefined) { market = this.market(symbol); symbol = market['symbol']; messageHash += ':' + market['id']; } const orders = await this.watchPrivate(messageHash, params); if (this.newUpdates) { limit = orders.getLimit(symbol, limit); } return this.filterBySymbolSinceLimit(orders, symbol, since, limit); } handleOrder(client, message, subscription = undefined) { // // { // topic: 'order', // action: 'insert', // user_id: 155328, // symbol: 'ltc-usdt', // data: { // symbol: 'ltc-usdt', // side: 'buy', // size: 0.05, // type: 'market', // price: 0, // fee_structure: { maker: 0.1, taker: 0.1 }, // fee_coin: 'ltc', // id: 'ce38fd48-b336-400b-812b-60c636454231', // created_by: 155328, // filled: 0.05, // method: 'market', // created_at: '2022-04-11T14:09:00.760Z', // updated_at: '2022-04-11T14:09:00.760Z', // status: 'filled' // }, // time: 1649686140 // } // // { // "topic":"order", // "action":"partial", // "user_id":155328, // "data":[ // { // "created_at":"2022-05-13T08:19:07.694Z", // "fee":0, // "meta":{ // // }, // "symbol":"ltc-usdt", // "side":"buy", // "size":0.1, // "type":"limit", // "price":55, // "fee_structure":{ // "maker":0.1, // "taker":0.1 // }, // "fee_coin":"ltc", // "id":"d5e77182-ad4c-4ac9-8ce4-a97f9b43e33c", // "created_by":155328, // "filled":0, // "status":"new", // "updated_at":"2022-05-13T08:19:07.694Z", // "stop":null // } // ], // "time":1652430035 // } // const channel = this.safeString(message, 'topic'); const data = this.safeValue(message, 'data', {}); // usually the first message is an empty array const dataLength = data.length; if (dataLength === 0) { return 0; } if (this.orders === undefined) { const limit = this.safeInteger(this.options, 'ordersLimit', 1000); this.orders = new ArrayCacheBySymbolById(limit); } const stored = this.orders; let rawOrders = undefined; if (!Array.isArray(data)) { rawOrders = [data]; } else { rawOrders = data; } const marketIds = {}; for (let i = 0; i < rawOrders.length; i++) { const order = rawOrders[i]; const parsed = this.parseOrder(order); stored.append(parsed); const symbol = order['symbol']; const market = this.market(symbol); const marketId = market['id']; marketIds[marketId] = true; } // non-symbol specific client.resolve(this.orders, channel); const keys = Object.keys(marketIds); for (let i = 0; i < keys.length; i++) { const marketId = keys[i]; const messageHash = channel + ':' + marketId; client.resolve(this.orders, messageHash); } } async watchBalance(params = {}) { /** * @method * @name hollaex#watchBalance * @description query for balance and get the amount of funds available for trading or funds locked in orders * @param {object} params extra parameters specific to the hollaex api endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure} */ const messageHash = 'wallet'; return await this.watchPrivate(messageHash, params); } handleBalance(client, message) { // // { // topic: 'wallet', // action: 'partial', // user_id: 155328, // data: { // eth_balance: 0, // eth_available: 0, // usdt_balance: 18.94344188, // usdt_available: 18.94344188, // ltc_balance: 0.00005, // ltc_available: 0.00005, // }, // time: 1649687396 // } // const messageHash = this.safeString(message, 'topic'); const data = this.safeValue(message, 'data'); const keys = Object.keys(data); const timestamp = this.safeIntegerProduct(message, 'time', 1000); this.balance['info'] = data; this.balance['timestamp'] = timestamp; this.balance['datetime'] = this.iso8601(timestamp); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const parts = key.split('_'); const currencyId = this.safeString(parts, 0); const code = this.safeCurrencyCode(currencyId); const account = (code in this.balance) ? this.balance[code] : this.account(); const second = this.safeString(parts, 1); const freeOrTotal = (second === 'available') ? 'free' : 'total'; account[freeOrTotal] = this.safeString(data, key); this.balance[code] = account; } this.balance = this.safeBalance(this.balance); client.resolve(this.balance, messageHash); } async watchPublic(messageHash, params = {}) { const url = this.urls['api']['ws']; const request = { 'op': 'subscribe', 'args': [messageHash], }; const message = this.extend(request, params); return await this.watch(url, messageHash, message, messageHash); } async watchPrivate(messageHash, params = {}) { this.checkRequiredCredentials(); let expires = this.safeString(this.options, 'ws-expires'); if (expires === undefined) { const timeout = parseInt((this.timeout / 1000).toString()); expires = this.sum(this.seconds(), timeout); expires = expires.toString(); // we need to memoize these values to avoid generating a new url on each method execution // that would trigger a new connection on each received message this.options['ws-expires'] = expires; } const url = this.urls['api']['ws']; const auth = 'CONNECT' + '/stream' + expires; const signature = this.hmac(this.encode(auth), this.encode(this.secret), sha256); const authParams = { 'api-key': this.apiKey, 'api-signature': signature, 'api-expires': expires, }; const signedUrl = url + '?' + this.urlencode(authParams); const request = { 'op': 'subscribe', 'args': [messageHash], }; const message = this.extend(request, params); return await this.watch(signedUrl, messageHash, message, messageHash); } handleErrorMessage(client, message) { // // { error: 'Bearer or HMAC authentication required' } // { error: 'Error: wrong input' } // const error = this.safeInteger(message, 'error'); try { if (error !== undefined) { const feedback = this.id + ' ' + this.json(message); this.throwExactlyMatchedException(this.exceptions['ws']['exact'], error, feedback); } } catch (e) { if (e instanceof AuthenticationError) { return false; } } return message; } handleMessage(client, message) { // // pong // // { message: 'pong' } // // trade // // { // topic: 'trade', // action: 'partial', // symbol: 'btc-usdt', // data: [ // { // size: 0.05145, // price: 41977.9, // side: 'buy', // timestamp: '2022-04-11T09:40:10.881Z' // }, // ] // } // // orderbook // // { // topic: 'orderbook', // action: 'partial', // symbol: 'ltc-usdt', // data: { // bids: [ // [104.29, 5.2264], // [103.86,1.3629], // [101.82,0.5942] // ], // asks: [ // [104.81,9.5531], // [105.54,0.6416], // [106.18,1.4141], // ], // timestamp: '2022-04-11T10:37:01.227Z' // }, // time: 1649673421 // } // // order // // { // topic: 'order', // action: 'insert', // user_id: 155328, // symbol: 'ltc-usdt', // data: { // symbol: 'ltc-usdt', // side: 'buy', // size: 0.05, // type: 'market', // price: 0, // fee_structure: { maker: 0.1, taker: 0.1 }, // fee_coin: 'ltc', // id: 'ce38fd48-b336-400b-812b-60c636454231', // created_by: 155328, // filled: 0.05, // method: 'market', // created_at: '2022-04-11T14:09:00.760Z', // updated_at: '2022-04-11T14:09:00.760Z', // status: 'filled' // }, // time: 1649686140 // } // // balance // // { // topic: 'wallet', // action: 'partial', // user_id: 155328, // data: { // eth_balance: 0, // eth_available: 0, // usdt_balance: 18.94344188, // usdt_available: 18.94344188, // ltc_balance: 0.00005, // ltc_available: 0.00005, // } // } // if (!this.handleErrorMessage(client, message)) { return; } const content = this.safeString(message, 'message'); if (content === 'pong') { this.handlePong(client, message); return; } const methods = { 'trade': this.handleTrades, 'orderbook': this.handleOrderBook, 'order': this.handleOrder, 'wallet': this.handleBalance, 'usertrade': this.handleMyTrades, }; const topic = this.safeValue(message, 'topic'); const method = this.safeValue(methods, topic); if (method !== undefined) { method.call(this, client, message); } } ping(client) { // hollaex does not support built-in ws protocol-level ping-pong return { 'op': 'ping' }; } handlePong(client, message) { client.lastPong = this.milliseconds(); return message; } onError(client, error) { this.options['ws-expires'] = undefined; this.onError(client, error); } onClose(client, error) { this.options['ws-expires'] = undefined; this.onClose(client, error); } }