UNPKG

sfccxt

Version:

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

1,221 lines (1,185 loc) 51.2 kB
'use strict'; // --------------------------------------------------------------------------- const gateRest = require ('../gate.js'); const { AuthenticationError, BadRequest, ArgumentsRequired, NotSupported, InvalidNonce } = require ('../base/errors'); const { ArrayCache, ArrayCacheByTimestamp, ArrayCacheBySymbolById } = require ('./base/Cache'); // --------------------------------------------------------------------------- module.exports = class gate extends gateRest { describe () { return this.deepExtend (super.describe (), { 'has': { 'ws': true, 'watchOrderBook': true, 'watchTicker': true, 'watchTickers': false, // for now 'watchTrades': true, 'watchMyTrades': true, 'watchOHLCV': true, 'watchBalance': true, 'watchOrders': true, }, 'urls': { 'api': { 'ws': 'wss://ws.gate.io/v4', 'spot': 'wss://api.gateio.ws/ws/v4/', 'swap': { 'usdt': 'wss://fx-ws.gateio.ws/v4/ws/usdt', 'btc': 'wss://fx-ws.gateio.ws/v4/ws/btc', }, 'future': { 'usdt': 'wss://fx-ws.gateio.ws/v4/ws/delivery/usdt', 'btc': 'wss://fx-ws.gateio.ws/v4/ws/delivery/btc', }, 'option': 'wss://op-ws.gateio.live/v4/ws', }, 'test': { 'swap': { 'usdt': 'wss://fx-ws-testnet.gateio.ws/v4/ws/usdt', 'btc': 'wss://fx-ws-testnet.gateio.ws/v4/ws/btc', }, 'future': { 'usdt': 'wss://fx-ws-testnet.gateio.ws/v4/ws/usdt', 'btc': 'wss://fx-ws-testnet.gateio.ws/v4/ws/btc', }, 'option': 'wss://op-ws-testnet.gateio.live/v4/ws', }, }, 'options': { 'tradesLimit': 1000, 'OHLCVLimit': 1000, 'watchTradesSubscriptions': {}, 'watchTickerSubscriptions': {}, 'watchOrderBookSubscriptions': {}, 'watchTicker': { 'name': 'tickers', // or book_ticker }, 'watchOrderBook': { 'interval': '100ms', }, 'watchBalance': { 'settle': 'usdt', // or btc 'spot': 'spot.balances', // spot.margin_balances, spot.funding_balances or spot.cross_balances }, }, 'exceptions': { 'ws': { 'exact': { '2': BadRequest, '4': AuthenticationError, '6': AuthenticationError, '11': AuthenticationError, }, }, }, }); } async watchOrderBook (symbol, limit = undefined, params = {}) { /** * @method * @name gate#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 gate api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure} indexed by market symbols */ await this.loadMarkets (); const market = this.market (symbol); symbol = market['symbol']; const marketId = market['id']; const options = this.safeValue (this.options, 'watchOrderBook', {}); const defaultLimit = this.safeInteger (options, 'limit', 20); if (!limit) { limit = defaultLimit; } const defaultInterval = this.safeString (options, 'interval', '100ms'); const interval = this.safeString (params, 'interval', defaultInterval); const type = market['type']; const messageType = this.getUniformType (type); const method = messageType + '.' + 'order_book_update'; const messageHash = method + ':' + market['symbol']; const url = this.getUrlByMarketType (type, market['inverse']); const payload = [ marketId, interval ]; if (type !== 'spot') { // contract pairs require limit in the payload const stringLimit = limit.toString (); payload.push (stringLimit); } const subscriptionParams = { 'method': this.handleOrderBookSubscription, 'symbol': symbol, 'limit': limit, }; const orderbook = await this.subscribePublic (url, method, messageHash, payload, subscriptionParams); return orderbook.limit (); } 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); const options = this.safeValue (this.options, 'handleOrderBookSubscription', {}); const fetchOrderBookSnapshot = this.safeValue (options, 'fetchOrderBookSnapshot', false); if (fetchOrderBookSnapshot) { const fetchingOrderBookSnapshot = 'fetchingOrderBookSnapshot'; subscription[fetchingOrderBookSnapshot] = true; const messageHash = subscription['messageHash']; client.subscriptions[messageHash] = subscription; this.spawn (this.fetchOrderBookSnapshot, client, message, subscription); } } async fetchOrderBookSnapshot (client, message, subscription) { const symbol = this.safeString (subscription, 'symbol'); const limit = this.safeInteger (subscription, 'limit'); const messageHash = this.safeString (subscription, 'messageHash'); try { const snapshot = await this.fetchOrderBook (symbol, limit); const orderbook = this.orderbooks[symbol]; const messages = orderbook.cache; const firstMessage = this.safeValue (messages, 0, {}); const result = this.safeValue (firstMessage, 'result'); const seqNum = this.safeInteger (result, 'U'); const nonce = this.safeInteger (snapshot, 'nonce'); // if the received snapshot is earlier than the first cached delta // then we cannot align it with the cached deltas and we need to // retry synchronizing in maxAttempts if ((seqNum === undefined) || (nonce < seqNum)) { const maxAttempts = this.safeInteger (this.options, 'maxOrderBookSyncAttempts', 3); let numAttempts = this.safeInteger (subscription, 'numAttempts', 0); // retry to synchronize if we haven't reached maxAttempts yet if (numAttempts < maxAttempts) { // safety guard if (messageHash in client.subscriptions) { numAttempts = this.sum (numAttempts, 1); subscription['numAttempts'] = numAttempts; client.subscriptions[messageHash] = subscription; this.spawn (this.fetchOrderBookSnapshot, client, message, subscription); } } else { // throw upon failing to synchronize in maxAttempts delete client.subscriptions[messageHash]; throw new InvalidNonce (this.id + ' failed to synchronize WebSocket feed with the snapshot for symbol ' + symbol + ' in ' + maxAttempts.toString () + ' attempts'); } } else { orderbook.reset (snapshot); // unroll the accumulated deltas for (let i = 0; i < messages.length; i++) { const message = messages[i]; this.handleOrderBookMessage (client, message, orderbook); } this.orderbooks[symbol] = orderbook; client.resolve (orderbook, messageHash); } } catch (e) { client.reject (e, messageHash); } } handleOrderBook (client, message) { // // { // "time":1649770575, // "channel":"spot.order_book_update", // "event":"update", // "result":{ // "t":1649770575537, // "e":"depthUpdate", // "E":1649770575, // "s":"LTC_USDT", // "U":2622528153, // "u":2622528265, // "b":[ // ["104.18","3.9398"], // ["104.56","19.0603"], // ["104.94","0"], // ["103.72","0"], // ["105.01","52.6186"], // ["104.76","0"], // ["104.97","0"], // ["104.71","0"], // ["104.84","25.8604"], // ["104.51","47.6508"], // ], // "a":[ // ["105.26","40.5519"], // ["106.08","35.4396"], // ["105.2","0"], // ["105.45","8.5834"], // ["105.5","20.17"], // ["105.11","54.8359"], // ["105.52","28.5605"], // ["105.27","6.6325"], // ["105.3","4.291446"], // ["106.03","9.712"], // ] // } // } // const channel = this.safeString (message, 'channel'); const result = this.safeValue (message, 'result'); const marketId = this.safeString (result, 's'); const symbol = this.safeSymbol (marketId); let orderbook = this.safeValue (this.orderbooks, symbol); if (orderbook === undefined) { orderbook = this.orderBook ({}); this.orderbooks[symbol] = orderbook; } const messageHash = channel + ':' + symbol; const subscription = this.safeValue (client.subscriptions, messageHash, {}); const fetchingOrderBookSnapshot = 'fetchingOrderBookSnapshot'; const isFetchingOrderBookSnapshot = this.safeValue (subscription, fetchingOrderBookSnapshot, false); if (!isFetchingOrderBookSnapshot) { subscription[fetchingOrderBookSnapshot] = true; client.subscriptions[messageHash] = subscription; this.spawn (this.fetchOrderBookSnapshot, client, message, subscription); } if (orderbook['nonce'] === undefined) { orderbook.cache.push (message); } else { const messageHash = channel + ':' + symbol; this.handleOrderBookMessage (client, message, orderbook, messageHash); } } handleOrderBookMessage (client, message, orderbook, messageHash = undefined) { // // spot // // { // time: 1650189272, // channel: 'spot.order_book_update', // event: 'update', // result: { // t: 1650189272515, // e: 'depthUpdate', // E: 1650189272, // s: 'GMT_USDT', // U: 140595902, // u: 140595902, // b: [ // [ '2.51518', '228.119' ], // [ '2.50587', '1510.11' ], // [ '2.49944', '67.6' ], // ], // a: [ // [ '2.5182', '4.199' ], // [ '2.51926', '1874' ], // [ '2.53528', '96.529' ], // ] // } // } // // swap // // { // id: null, // time: 1650188898, // channel: 'futures.order_book_update', // event: 'update', // error: null, // result: { // t: 1650188898938, // s: 'GMT_USDT', // U: 1577718307, // u: 1577719254, // b: [ // { p: '2.5178', s: 0 }, // { p: '2.5179', s: 0 }, // { p: '2.518', s: 0 }, // ], // a: [ // { p: '2.52', s: 0 }, // { p: '2.5201', s: 0 }, // { p: '2.5203', s: 0 }, // ] // } // } // const result = this.safeValue (message, 'result'); const seqNum = this.safeInteger (result, 'u'); const nonce = orderbook['nonce']; // we can't use the prevSeqNum (U) here because it is not consistent // with the previous message sometimes so if the current seqNum // is 2 in the next message might be 3 or 4... so it is not safe to use if (seqNum >= nonce) { const asks = this.safeValue (result, 'a', []); const bids = this.safeValue (result, 'b', []); this.handleDeltas (orderbook['asks'], asks); this.handleDeltas (orderbook['bids'], bids); orderbook['nonce'] = seqNum; const timestamp = this.safeInteger (result, 't'); orderbook['timestamp'] = timestamp; orderbook['datetime'] = this.iso8601 (timestamp); if (messageHash !== undefined) { client.resolve (orderbook, messageHash); } } return orderbook; } handleDelta (bookside, delta) { let price = undefined; let amount = undefined; if (Array.isArray (delta)) { // spot price = this.safeFloat (delta, 0); amount = this.safeFloat (delta, 1); } else { // swap price = this.safeFloat (delta, 'p'); amount = this.safeFloat (delta, 's'); } bookside.store (price, amount); } handleDeltas (bookside, deltas) { for (let i = 0; i < deltas.length; i++) { this.handleDelta (bookside, deltas[i]); } } async watchTicker (symbol, params = {}) { /** * @method * @name gate#watchTicker * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} params extra parameters specific to the gate api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure} */ await this.loadMarkets (); const market = this.market (symbol); const marketId = market['id']; const type = market['type']; const messageType = this.getUniformType (type); const options = this.safeValue (this.options, 'watchTicker', {}); const topic = this.safeString (options, 'name', 'tickers'); const channel = messageType + '.' + topic; const messageHash = channel + '.' + market['symbol']; const payload = [ marketId ]; const url = this.getUrlByMarketType (type, market['inverse']); return await this.subscribePublic (url, channel, messageHash, payload); } handleTicker (client, message) { // // { // time: 1649326221, // channel: 'spot.tickers', // event: 'update', // result: { // currency_pair: 'BTC_USDT', // last: '43444.82', // lowest_ask: '43444.82', // highest_bid: '43444.81', // change_percentage: '-4.0036', // base_volume: '5182.5412425462', // quote_volume: '227267634.93123952', // high_24h: '47698', // low_24h: '42721.03' // } // } // { // time: 1671363004, // time_ms: 1671363004235, // channel: 'spot.book_ticker', // event: 'update', // result: { // t: 1671363004228, // u: 9793320464, // s: 'BTC_USDT', // b: '16716.8', // B: '0.0134', // a: '16716.9', // A: '0.0353' // } // } // const channel = this.safeString (message, 'channel'); let result = this.safeValue (message, 'result'); if (!Array.isArray (result)) { result = [ result ]; } for (let i = 0; i < result.length; i++) { const ticker = result[i]; const parsed = this.parseTicker (ticker); const symbol = parsed['symbol']; this.tickers[symbol] = parsed; const messageHash = channel + '.' + symbol; client.resolve (this.tickers[symbol], messageHash); } } async watchTrades (symbol, since = undefined, limit = undefined, params = {}) { /** * @method * @name gate#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 gate 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 marketId = market['id']; const type = market['type']; const messageType = this.getUniformType (type); const method = messageType + '.trades'; let messageHash = method; if (symbol !== undefined) { messageHash += ':' + market['symbol']; } const url = this.getUrlByMarketType (type, market['inverse']); const payload = [ marketId ]; const trades = await this.subscribePublic (url, method, messageHash, payload); if (this.newUpdates) { limit = trades.getLimit (symbol, limit); } return this.filterBySinceLimit (trades, since, limit, 'timestamp', true); } handleTrades (client, message) { // // { // time: 1648725035, // channel: 'spot.trades', // event: 'update', // result: [{ // id: 3130257995, // create_time: 1648725035, // create_time_ms: '1648725035923.0', // side: 'sell', // currency_pair: 'LTC_USDT', // amount: '0.0116', // price: '130.11' // }] // } // const channel = this.safeString (message, 'channel'); let result = this.safeValue (message, 'result'); if (!Array.isArray (result)) { result = [ result ]; } const parsedTrades = this.parseTrades (result); const marketIds = {}; for (let i = 0; i < parsedTrades.length; i++) { const trade = parsedTrades[i]; const symbol = trade['symbol']; let cachedTrades = this.safeValue (this.trades, symbol); if (cachedTrades === undefined) { const limit = this.safeInteger (this.options, 'tradesLimit', 1000); cachedTrades = new ArrayCache (limit); this.trades[symbol] = cachedTrades; } cachedTrades.append (trade); marketIds[symbol] = true; } const keys = Object.keys (marketIds); for (let i = 0; i < keys.length; i++) { const symbol = keys[i]; const hash = channel + ':' + symbol; const stored = this.safeValue (this.trades, symbol); client.resolve (stored, hash); } client.resolve (this.trades, channel); } async watchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name gate#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|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 gate 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']; const marketId = market['id']; const type = market['type']; const interval = this.timeframes[timeframe]; const messageType = this.getUniformType (type); const method = messageType + '.candlesticks'; const messageHash = method + ':' + interval + ':' + market['symbol']; const url = this.getUrlByMarketType (type, market['inverse']); const payload = [ interval, marketId ]; const ohlcv = await this.subscribePublic (url, method, messageHash, payload); if (this.newUpdates) { limit = ohlcv.getLimit (symbol, limit); } return this.filterBySinceLimit (ohlcv, since, limit, 0, true); } handleOHLCV (client, message) { // // { // "time": 1606292600, // "channel": "spot.candlesticks", // "event": "update", // "result": { // "t": "1606292580", // total volume // "v": "2362.32035", // volume // "c": "19128.1", // close // "h": "19128.1", // high // "l": "19128.1", // low // "o": "19128.1", // open // "n": "1m_BTC_USDT" // sub // } // } // const channel = this.safeString (message, 'channel'); let result = this.safeValue (message, 'result'); const isArray = Array.isArray (result); if (!isArray) { result = [ result ]; } const marketIds = {}; for (let i = 0; i < result.length; i++) { const ohlcv = result[i]; const subscription = this.safeString (ohlcv, 'n', ''); const parts = subscription.split ('_'); const timeframe = this.safeString (parts, 0); const prefix = timeframe + '_'; const marketId = subscription.replace (prefix, ''); const symbol = this.safeSymbol (marketId, undefined, '_'); const parsed = this.parseOHLCV (ohlcv); let stored = this.safeValue (this.ohlcvs, symbol); if (stored === undefined) { const limit = this.safeInteger (this.options, 'OHLCVLimit', 1000); stored = new ArrayCacheByTimestamp (limit); this.ohlcvs[symbol] = stored; } stored.append (parsed); marketIds[symbol] = timeframe; } const keys = Object.keys (marketIds); for (let i = 0; i < keys.length; i++) { const symbol = keys[i]; const timeframe = marketIds[symbol]; const interval = this.timeframes[timeframe]; const hash = channel + ':' + interval + ':' + symbol; const stored = this.safeValue (this.ohlcvs, symbol); client.resolve (stored, hash); } } async authenticate (params = {}) { const url = this.urls['api']['ws']; const client = this.client (url); const future = client.future ('authenticated'); const method = 'server.sign'; const authenticate = this.safeValue (client.subscriptions, method); if (authenticate === undefined) { const requestId = this.milliseconds (); const requestIdString = requestId.toString (); const signature = this.hmac (this.encode (requestIdString), this.encode (this.secret), 'sha512', 'hex'); const authenticateMessage = { 'id': requestId, 'method': method, 'params': [ this.apiKey, signature, requestId ], }; const subscribe = { 'id': requestId, 'method': this.handleAuthenticationMessage, }; this.spawn (this.watch, url, requestId, authenticateMessage, method, subscribe); } return await future; } async watchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name gate#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 gate api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure */ await this.loadMarkets (); let subType = undefined; let type = undefined; let marketId = '!' + 'all'; if (symbol !== undefined) { const market = this.market (symbol); symbol = market['symbol']; type = market['type']; marketId = market['id']; } else { [ type, params ] = this.handleMarketTypeAndParams ('watchMyTrades', undefined, params); if (type !== 'spot') { const options = this.safeValue (this.options, 'watchMyTrades', {}); subType = this.safeValue (options, 'subType', 'linear'); subType = this.safeValue (params, 'subType', subType); params = this.omit (params, 'subType'); } } const messageType = this.getUniformType (type); const method = messageType + '.usertrades'; let messageHash = method; if (symbol !== undefined) { messageHash += ':' + symbol; } const isInverse = (subType === 'inverse'); const url = this.getUrlByMarketType (type, isInverse); const payload = [ marketId ]; // uid required for non spot markets const requiresUid = (type !== 'spot'); const trades = await this.subscribePrivate (url, method, messageHash, payload, requiresUid); if (this.newUpdates) { limit = trades.getLimit (symbol, limit); } return this.filterBySymbolSinceLimit (trades, symbol, since, limit, true); } handleMyTrades (client, message) { // // { // "time": 1543205083, // "channel": "futures.usertrades", // "event": "update", // "error": null, // "result": [ // { // "id": "3335259", // "create_time": 1628736848, // "create_time_ms": 1628736848321, // "contract": "BTC_USD", // "order_id": "4872460", // "size": 1, // "price": "40000.4", // "role": "maker" // } // ] // } // const result = this.safeValue (message, 'result', []); const channel = this.safeString (message, 'channel'); const tradesLength = result.length; if (tradesLength === 0) { return; } let cachedTrades = this.myTrades; if (cachedTrades === undefined) { const limit = this.safeInteger (this.options, 'tradesLimit', 1000); cachedTrades = new ArrayCacheBySymbolById (limit); this.myTrades = cachedTrades; } const parsed = this.parseTrades (result); const marketIds = {}; for (let i = 0; i < parsed.length; i++) { const trade = parsed[i]; cachedTrades.append (trade); const symbol = trade['symbol']; marketIds[symbol] = true; } const keys = Object.keys (marketIds); for (let i = 0; i < keys.length; i++) { const market = keys[i]; const hash = channel + ':' + market; client.resolve (cachedTrades, hash); } client.resolve (cachedTrades, channel); } async watchBalance (params = {}) { /** * @method * @name gate#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 gate api endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure} */ await this.loadMarkets (); let type = undefined; [ type, params ] = this.handleMarketTypeAndParams ('watchBalance', undefined, params); const options = this.safeValue (this.options, 'watchBalance', {}); let subType = this.safeValue (options, 'subType', 'linear'); subType = this.safeValue (params, 'subType', subType); params = this.omit (params, 'subType'); const isInverse = (subType === 'inverse'); const url = this.getUrlByMarketType (type, isInverse); const requiresUid = (type !== 'spot'); let channelType = 'spot'; if (type === 'future' || type === 'swap') { channelType = 'futures'; } else if (type === 'option') { channelType = 'options'; } let channel = undefined; if (type === 'spot') { const options = this.safeValue (this.options, 'watchTicker', {}); channel = this.safeString (options, 'spot', 'spot.balances'); } else { channel = channelType + '.balances'; } return await this.subscribePrivate (url, channel, channel, undefined, requiresUid); } handleBalance (client, message) { const messageHash = message['method']; const result = message['params'][0]; this.handleBalanceMessage (client, messageHash, result); } handleBalanceMessage (client, message) { // // spot order fill // { // time: 1653664351, // channel: 'spot.balances', // event: 'update', // result: [ // { // timestamp: '1653664351', // timestamp_ms: '1653664351017', // user: '10406147', // currency: 'LTC', // change: '-0.0002000000000000', // total: '0.09986000000000000000', // available: '0.09986000000000000000' // } // ] // } // // account transfer // // { // id: null, // time: 1653665088, // channel: 'futures.balances', // event: 'update', // error: null, // result: [ // { // balance: 25.035008537, // change: 25, // text: '-', // time: 1653665088, // time_ms: 1653665088286, // type: 'dnw', // user: '10406147' // } // ] // } // // swap order fill // { // id: null, // time: 1653665311, // channel: 'futures.balances', // event: 'update', // error: null, // result: [ // { // balance: 20.031873037, // change: -0.0031355, // text: 'LTC_USDT:165551103273', // time: 1653665311, // time_ms: 1653665311437, // type: 'fee', // user: '10406147' // } // ] // } // const channel = this.safeString (message, 'channel'); const result = this.safeValue (message, 'result', []); for (let i = 0; i < result.length; i++) { const rawBalance = result[i]; const account = this.account (); const currencyId = this.safeString (rawBalance, 'currency', 'USDT'); // when not present it is USDT const code = this.safeCurrencyCode (currencyId); account['free'] = this.safeString (rawBalance, 'available'); account['total'] = this.safeString2 (rawBalance, 'total', 'balance'); this.balance[code] = account; } this.balance = this.safeBalance (this.balance); client.resolve (this.balance, channel); } async watchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name gate#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 gate api endpoint * @param {string} params.type spot, margin, swap, future, or option. Required if listening to all symbols. * @param {boolean} params.isInverse if future, listen to inverse or linear contracts * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure */ await this.loadMarkets (); let market = undefined; if (symbol !== undefined) { market = this.market (symbol); symbol = market['symbol']; } let type = undefined; let query = undefined; [ type, query ] = this.handleMarketTypeAndParams ('watchOrders', market, params); const typeId = this.getSupportedMapping (type, { 'spot': 'spot', 'margin': 'spot', 'future': 'futures', 'swap': 'futures', 'option': 'options', }); const method = typeId + '.orders'; let messageHash = method; let payload = [ '!' + 'all' ]; if (symbol !== undefined) { messageHash = method + ':' + market['id']; payload = [ market['id'] ]; } let subType = undefined; [ subType, query ] = this.handleSubTypeAndParams ('watchOrders', market, query); const isInverse = (subType === 'inverse'); const url = this.getUrlByMarketType (type, isInverse); // uid required for non spot markets const requiresUid = (type !== 'spot'); const orders = await this.subscribePrivate (url, method, messageHash, payload, requiresUid); if (this.newUpdates) { limit = orders.getLimit (symbol, limit); } return this.filterBySinceLimit (orders, since, limit, 'timestamp', true); } handleOrder (client, message) { // // { // "time": 1605175506, // "channel": "spot.orders", // "event": "update", // "result": [ // { // "id": "30784435", // "user": 123456, // "text": "t-abc", // "create_time": "1605175506", // "create_time_ms": "1605175506123", // "update_time": "1605175506", // "update_time_ms": "1605175506123", // "event": "put", // "currency_pair": "BTC_USDT", // "type": "limit", // "account": "spot", // "side": "sell", // "amount": "1", // "price": "10001", // "time_in_force": "gtc", // "left": "1", // "filled_total": "0", // "fee": "0", // "fee_currency": "USDT", // "point_fee": "0", // "gt_fee": "0", // "gt_discount": true, // "rebated_fee": "0", // "rebated_fee_currency": "USDT" // } // ] // } // const orders = this.safeValue (message, 'result', []); const channel = this.safeString (message, 'channel'); const ordersLength = orders.length; if (ordersLength > 0) { const limit = this.safeInteger (this.options, 'ordersLimit', 1000); if (this.orders === undefined) { this.orders = new ArrayCacheBySymbolById (limit); } const stored = this.orders; const marketIds = {}; const parsedOrders = this.parseOrders (orders); for (let i = 0; i < parsedOrders.length; i++) { const parsed = parsedOrders[i]; // inject order status const info = this.safeValue (parsed, 'info'); const event = this.safeString (info, 'event'); if (event === 'put') { parsed['status'] = 'open'; } else if (event === 'finish') { parsed['status'] = 'closed'; } stored.append (parsed); const symbol = parsed['symbol']; const market = this.market (symbol); marketIds[market['id']] = true; } const keys = Object.keys (marketIds); for (let i = 0; i < keys.length; i++) { const messageHash = channel + ':' + keys[i]; client.resolve (this.orders, messageHash); } client.resolve (this.orders, channel); } } handleAuthenticationMessage (client, message, subscription) { const result = this.safeValue (message, 'result'); const status = this.safeString (result, 'status'); if (status === 'success') { // client.resolve (true, 'authenticated') will delete the future // we want to remember that we are authenticated in subsequent call to private methods const future = this.safeValue (client.futures, 'authenticated'); if (future !== undefined) { future.resolve (true); } } else { // delete authenticate subscribeHash to release the "subscribe lock" // allows subsequent calls to subscribe to reauthenticate // avoids sending two authentication messages before receiving a reply const error = new AuthenticationError (this.id + ' handleAuthenticationMessage() error'); client.reject (error, 'authenticated'); if ('server.sign' in client.subscriptions) { delete client.subscriptions['server.sign']; } } } handleErrorMessage (client, message) { // { // time: 1647274664, // channel: 'futures.orders', // event: 'subscribe', // error: { code: 2, message: 'unknown contract BTC_USDT_20220318' }, // } // { // time: 1647276473, // channel: 'futures.orders', // event: 'subscribe', // error: { // code: 4, // message: '{"label":"INVALID_KEY","message":"Invalid key provided"}\n' // }, // result: null // } const error = this.safeValue (message, 'error', {}); const code = this.safeInteger (error, 'code'); if (code !== undefined) { const id = this.safeString (message, 'id'); const subscriptionsById = this.indexBy (client.subscriptions, 'id'); const subscription = this.safeValue (subscriptionsById, id); if (subscription !== undefined) { try { this.throwExactlyMatchedException (this.exceptions['ws']['exact'], code, this.json (message)); } catch (e) { const messageHash = this.safeString (subscription, 'messageHash'); client.reject (e, messageHash); client.reject (e, id); if (id in client.subscriptions) { delete client.subscriptions[id]; } } } } } handleBalanceSubscription (client, message) { this.spawn (this.fetchBalanceSnapshot, client, message); } async fetchBalanceSnapshot (client, message) { // // { // id: 1, // time: 1653665810, // channel: 'futures.balances', // event: 'subscribe', // auth: { // }, // payload: [ '10406147' ] // } // await this.loadMarkets (); const channel = this.safeString (message, 'channel', ''); const parts = channel.split ('.'); const exchangeType = this.safeString (parts, 0); let type = exchangeType; if (exchangeType === 'futures') { type = 'future'; } else if (type === 'options') { type = 'option'; } const params = { 'type': type, }; if (type === 'future' || type === 'swap') { const options = this.safeValue (this.options, 'watchTicker', {}); const settle = this.safeString (options, 'settle', 'usdt'); params['settle'] = settle; } const snapshot = await this.fetchBalance (params); this.balance = snapshot; client.resolve (this.balance, channel); } handleSubscriptionStatus (client, message) { const channel = this.safeString (message, 'channel', ''); if (channel.indexOf ('balance') >= 0) { this.handleBalanceSubscription (client, message); } } handleMessage (client, message) { // // subscribe // { // time: 1649062304, // id: 1649062303, // channel: 'spot.candlesticks', // event: 'subscribe', // result: { status: 'success' } // } // // candlestick // { // time: 1649063328, // channel: 'spot.candlesticks', // event: 'update', // result: { // t: '1649063280', // v: '58932.23174896', // c: '45966.47', // h: '45997.24', // l: '45966.47', // o: '45975.18', // n: '1m_BTC_USDT', // a: '1.281699' // } // } // // orders // { // "time": 1630654851, // "channel": "options.orders", or futures.orders or spot.orders // "event": "update", // "result": [ // { // "contract": "BTC_USDT-20211130-65000-C", // "create_time": 1637897000, // (...) // ] // } // orderbook // { // time: 1649770525, // channel: 'spot.order_book_update', // event: 'update', // result: { // t: 1649770525653, // e: 'depthUpdate', // E: 1649770525, // s: 'LTC_USDT', // U: 2622525645, // u: 2622525665, // b: [ // [Array], [Array], // [Array], [Array], // [Array], [Array], // [Array], [Array], // [Array], [Array], // [Array] // ], // a: [ // [Array], [Array], // [Array], [Array], // [Array], [Array], // [Array], [Array], // [Array], [Array], // [Array] // ] // } // } // // balance update // // { // time: 1653664351, // channel: 'spot.balances', // event: 'update', // result: [ // { // timestamp: '1653664351', // timestamp_ms: '1653664351017', // user: '10406147', // currency: 'LTC', // change: '-0.0002000000000000', // total: '0.09986000000000000000', // available: '0.09986000000000000000' // } // ] // } // this.handleErrorMessage (client, message); const event = this.safeString (message, 'event'); if (event === 'subscribe') { this.handleSubscriptionStatus (client, message); return; } const channel = this.safeString (message, 'channel', ''); const channelParts = channel.split ('.'); const channelType = this.safeValue (channelParts, 1); const v4Methods = { 'usertrades': this.handleMyTrades, 'candlesticks': this.handleOHLCV, 'orders': this.handleOrder, 'tickers': this.handleTicker, 'book_ticker': this.handleTicker, 'trades': this.handleTrades, 'order_book_update': this.handleOrderBook, 'balances': this.handleBalanceMessage, }; const method = this.safeValue (v4Methods, channelType); if (method !== undefined) { method.call (this, client, message); } } getUniformType (type) { let uniformType = 'spot'; if (type === 'future' || type === 'swap') { uniformType = 'futures'; } else if (type === 'option') { uniformType = 'options'; } return uniformType; } getUrlByMarketType (type, isInverse = false) { if (type === 'spot') { const spotUrl = this.urls['api']['spot']; if (spotUrl === undefined) { throw new NotSupported (this.id + ' does not have a testnet for the ' + type + ' market type.'); } return spotUrl; } if (type === 'swap') { const baseUrl = this.urls['api']['swap']; return isInverse ? baseUrl['btc'] : baseUrl['usdt']; } if (type === 'future') { const baseUrl = this.urls['api']['future']; return isInverse ? baseUrl['btc'] : baseUrl['usdt']; } if (type === 'option') { return this.urls['api']['option']; } } requestId () { // their support said that reqid must be an int32, not documented const reqid = this.sum (this.safeInteger (this.options, 'reqid', 0), 1); this.options['reqid'] = reqid; return reqid; } async subscribePublic (url, channel, messageHash, payload, subscriptionParams = {}) { const requestId = this.requestId (); const time = this.seconds (); const request = { 'id': requestId, 'time': time, 'channel': channel, 'event': 'subscribe', 'payload': payload, }; let subscription = { 'id': requestId, 'messageHash': messageHash, }; subscription = this.extend (subscription, subscriptionParams); return await this.watch (url, messageHash, request, messageHash, subscription); } async subscribePrivate (url, channel, messageHash, payload = undefined, requiresUid = false) { this.checkRequiredCredentials (); // uid is required for some subscriptions only so it's not a part of required credentials if (requiresUid) { if (this.uid === undefined || this.uid.length === 0) { throw new ArgumentsRequired (this.id + ' requi