ccxt
Version:
1,065 lines (1,063 loc) • 56.4 kB
JavaScript
// ----------------------------------------------------------------------------
// 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 xtRest from '../xt.js';
import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp } from '../base/ws/Cache.js';
// ---------------------------------------------------------------------------
export default class xt extends xtRest {
describe() {
return this.deepExtend(super.describe(), {
'has': {
'ws': true,
'watchOHLCV': true,
'watchOrderBook': true,
'watchTicker': true,
'watchTickers': true,
'watchTrades': true,
'watchTradesForSymbols': false,
'watchBalance': true,
'watchOrders': true,
'watchMyTrades': true,
'watchPositions': true,
},
'urls': {
'api': {
'ws': {
'spot': 'wss://stream.xt.com',
'contract': 'wss://fstream.xt.com/ws',
},
},
},
'options': {
'tradesLimit': 1000,
'ordersLimit': 1000,
'OHLCVLimit': 1000,
'watchTicker': {
'method': 'ticker', // agg_ticker (contract only)
},
'watchTickers': {
'method': 'tickers', // agg_tickers (contract only)
},
'watchPositions': {
'type': 'swap',
'fetchPositionsSnapshot': true,
'awaitPositionsSnapshot': true,
},
},
'streaming': {
'keepAlive': 20000,
'ping': this.ping,
},
'token': undefined,
});
}
/**
* @ignore
* @method
* @description required for private endpoints
* @param {string} isContract true for contract trades
* @see https://doc.xt.com/#websocket_privategetToken
* @see https://doc.xt.com/#futures_user_websocket_v2base
* @returns {string} listen key / access token
*/
async getListenKey(isContract) {
this.checkRequiredCredentials();
const tradeType = isContract ? 'contract' : 'spot';
let url = this.urls['api']['ws'][tradeType];
if (!isContract) {
url = url + '/private';
}
const client = this.client(url);
const token = this.safeString(client.subscriptions, 'token');
if (token === undefined) {
if (isContract) {
const response = await this.privateLinearGetFutureUserV1UserListenKey();
//
// {
// returnCode: '0',
// msgInfo: 'success',
// error: null,
// result: '3BC1D71D6CF96DA3458FC35B05B633351684511731128'
// }
//
client.subscriptions['token'] = this.safeString(response, 'result');
}
else {
const response = await this.privateSpotPostWsToken();
//
// {
// "rc": 0,
// "mc": "SUCCESS",
// "ma": [],
// "result": {
// "token": "eyJhbqGciOiJSUzI1NiJ9.eyJhY2NvdW50SWQiOiIyMTQ2Mjg1MzIyNTU5Iiwic3ViIjoibGh4dDRfMDAwMUBzbmFwbWFpbC5jYyIsInNjb3BlIjoiYXV0aCIsImlzcyI6Inh0LmNvbSIsImxhc3RBdXRoVGltZSI6MTY2MzgxMzY5MDk1NSwic2lnblR5cGUiOiJBSyIsInVzZXJOYW1lIjoibGh4dDRfMDAwMUBzbmFwbWFpbC5jYyIsImV4cCI6MTY2NjQwNTY5MCwiZGV2aWNlIjoidW5rbm93biIsInVzZXJJZCI6MjE0NjI4NTMyMjU1OX0.h3zJlJBQrK2x1HvUxsKivnn6PlSrSDXXXJ7WqHAYSrN2CG5XPTKc4zKnTVoYFbg6fTS0u1fT8wH7wXqcLWXX71vm0YuP8PCvdPAkUIq4-HyzltbPr5uDYd0UByx0FPQtq1exvsQGe7evXQuDXx3SEJXxEqUbq_DNlXPTq_JyScI",
// "refreshToken": "eyJhbGciOiqJSUzI1NiJ9.eyJhY2NvdW50SWQiOiIyMTQ2Mjg1MzIyNTU5Iiwic3ViIjoibGh4dDRfMDAwMUBzbmFwbWFpbC5jYyIsInNjb3BlIjoicmVmcmVzaCIsImlzcyI6Inh0LmNvbSIsImxhc3RBdXRoVGltZSI6MTY2MzgxMzY5MDk1NSwic2lnblR5cGUiOiJBSyIsInVzZXJOYW1lIjoibGh4dDRfMDAwMUBzbmFwbWFpbC5jYyIsImV4cCI6MTY2NjQwNTY5MCwiZGV2aWNlIjoidW5rbm93biIsInVzZXJJZCI6MjE0NjI4NTMyMjU1OX0.Fs3YVm5YrEOzzYOSQYETSmt9iwxUHBovh2u73liv1hLUec683WGfktA_s28gMk4NCpZKFeQWFii623FvdfNoteXR0v1yZ2519uNvNndtuZICDdv3BQ4wzW1wIHZa1skxFfqvsDnGdXpjqu9UFSbtHwxprxeYfnxChNk4ssei430"
// }
// }
//
const result = this.safeDict(response, 'result');
client.subscriptions['token'] = this.safeString(result, 'accessToken');
}
}
return client.subscriptions['token'];
}
getCacheIndex(orderbook, cache) {
// return the first index of the cache that can be applied to the orderbook or -1 if not possible
const nonce = this.safeInteger(orderbook, 'nonce');
const firstDelta = this.safeValue(cache, 0);
const firstDeltaNonce = this.safeInteger2(firstDelta, 'i', 'u');
if (nonce < firstDeltaNonce - 1) {
return -1;
}
for (let i = 0; i < cache.length; i++) {
const delta = cache[i];
const deltaNonce = this.safeInteger2(delta, 'i', 'u');
if (deltaNonce >= nonce) {
return i;
}
}
return cache.length;
}
handleDelta(orderbook, delta) {
orderbook['nonce'] = this.safeInteger2(delta, 'i', 'u');
const obAsks = this.safeList(delta, 'a', []);
const obBids = this.safeList(delta, 'b', []);
const bids = orderbook['bids'];
const asks = orderbook['asks'];
for (let i = 0; i < obBids.length; i++) {
const bid = obBids[i];
const price = this.safeNumber(bid, 0);
const quantity = this.safeNumber(bid, 1);
bids.store(price, quantity);
}
for (let i = 0; i < obAsks.length; i++) {
const ask = obAsks[i];
const price = this.safeNumber(ask, 0);
const quantity = this.safeNumber(ask, 1);
asks.store(price, quantity);
}
// this.handleBidAsks (storedBids, bids);
// this.handleBidAsks (storedAsks, asks);
}
/**
* @ignore
* @method
* @description Connects to a websocket channel
* @see https://doc.xt.com/#websocket_privaterequestFormat
* @see https://doc.xt.com/#futures_market_websocket_v2base
* @param {string} name name of the channel
* @param {string} access public or private
* @param {string} methodName the name of the CCXT class method
* @param {object} [market] CCXT market
* @param {string[]} [symbols] unified market symbols
* @param {object} params extra parameters specific to the xt api
* @returns {object} data from the websocket stream
*/
async subscribe(name, access, methodName, market = undefined, symbols = undefined, params = {}) {
const privateAccess = access === 'private';
let type = undefined;
[type, params] = this.handleMarketTypeAndParams(methodName, market, params);
const isContract = (type !== 'spot');
const subscribe = {
'method': isContract ? 'SUBSCRIBE' : 'subscribe',
'id': this.numberToString(this.milliseconds()) + name, // call back ID
};
if (privateAccess) {
if (!isContract) {
subscribe['params'] = [name];
subscribe['listenKey'] = await this.getListenKey(isContract);
}
else {
const listenKey = await this.getListenKey(isContract);
const param = name + '@' + listenKey;
subscribe['params'] = [param];
}
}
else {
subscribe['params'] = [name];
}
const tradeType = isContract ? 'contract' : 'spot';
let messageHash = name + '::' + tradeType;
if (symbols !== undefined) {
messageHash = messageHash + '::' + symbols.join(',');
}
const request = this.extend(subscribe, params);
let tail = access;
if (isContract) {
tail = privateAccess ? 'user' : 'market';
}
const url = this.urls['api']['ws'][tradeType] + '/' + tail;
return await this.watch(url, messageHash, request, messageHash);
}
/**
* @method
* @name xt#watchTicker
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
* @see https://doc.xt.com/#websocket_publictickerRealTime
* @see https://doc.xt.com/#futures_market_websocket_v2tickerRealTime
* @see https://doc.xt.com/#futures_market_websocket_v2aggTickerRealTime
* @param {string} symbol unified symbol of the market to fetch the ticker for
* @param {object} params extra parameters specific to the xt api endpoint
* @param {string} [params.method] 'agg_ticker' (contract only) or 'ticker', default = 'ticker' - the endpoint that will be streamed
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure}
*/
async watchTicker(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const options = this.safeDict(this.options, 'watchTicker');
const defaultMethod = this.safeString(options, 'method', 'ticker');
const method = this.safeString(params, 'method', defaultMethod);
const name = method + '@' + market['id'];
return await this.subscribe(name, 'public', 'watchTicker', market, undefined, params);
}
/**
* @method
* @name xt#watchTicker
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
* @see https://doc.xt.com/#websocket_publicallTicker
* @see https://doc.xt.com/#futures_market_websocket_v2allTicker
* @see https://doc.xt.com/#futures_market_websocket_v2allAggTicker
* @param {string} [symbols] unified market symbols
* @param {object} params extra parameters specific to the xt api endpoint
* @param {string} [params.method] 'agg_tickers' (contract only) or 'tickers', default = 'tickers' - the endpoint that will be streamed
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure}
*/
async watchTickers(symbols = undefined, params = {}) {
await this.loadMarkets();
const options = this.safeDict(this.options, 'watchTickers');
const defaultMethod = this.safeString(options, 'method', 'tickers');
const name = this.safeString(params, 'method', defaultMethod);
let market = undefined;
if (symbols !== undefined) {
market = this.market(symbols[0]);
}
const tickers = await this.subscribe(name, 'public', 'watchTickers', market, symbols, params);
if (this.newUpdates) {
return tickers;
}
return this.filterByArray(this.tickers, 'symbol', symbols);
}
/**
* @method
* @name hitbtc#watchOHLCV
* @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
* @see https://doc.xt.com/#websocket_publicsymbolKline
* @see https://doc.xt.com/#futures_market_websocket_v2symbolKline
* @param {string} symbol unified symbol of the market to fetch OHLCV data for
* @param {string} timeframe 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, or 1M
* @param {int} [since] not used by xt watchOHLCV
* @param {int} [limit] not used by xt watchOHLCV
* @param {object} params extra parameters specific to the xt 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 = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const name = 'kline@' + market['id'] + ',' + timeframe;
const ohlcv = await this.subscribe(name, 'public', 'watchOHLCV', market, undefined, params);
if (this.newUpdates) {
limit = ohlcv.getLimit(symbol, limit);
}
return this.filterBySinceLimit(ohlcv, since, limit, 0, true);
}
/**
* @method
* @name xt#watchTrades
* @description get the list of most recent trades for a particular symbol
* @see https://doc.xt.com/#websocket_publicdealRecord
* @see https://doc.xt.com/#futures_market_websocket_v2dealRecord
* @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 xt api endpoint
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html?#public-trades}
*/
async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const name = 'trade@' + market['id'];
const trades = await this.subscribe(name, 'public', 'watchTrades', market, undefined, params);
if (this.newUpdates) {
limit = trades.getLimit(symbol, limit);
}
return this.filterBySinceLimit(trades, since, limit, 'timestamp');
}
/**
* @method
* @name xt#watchOrderBook
* @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @see https://doc.xt.com/#websocket_publiclimitDepth
* @see https://doc.xt.com/#websocket_publicincreDepth
* @see https://doc.xt.com/#futures_market_websocket_v2limitDepth
* @see https://doc.xt.com/#futures_market_websocket_v2increDepth
* @param {string} symbol unified symbol of the market to fetch the order book for
* @param {int} [limit] not used by xt watchOrderBook
* @param {object} params extra parameters specific to the xt api endpoint
* @param {int} [params.levels] 5, 10, 20, or 50
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure} indexed by market symbols
*/
async watchOrderBook(symbol, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const levels = this.safeString(params, 'levels');
params = this.omit(params, 'levels');
let name = 'depth_update@' + market['id'];
if (levels !== undefined) {
name = 'depth@' + market['id'] + ',' + levels;
}
const orderbook = await this.subscribe(name, 'public', 'watchOrderBook', market, undefined, params);
return orderbook.limit();
}
/**
* @method
* @name xt#watchOrders
* @description watches information on multiple orders made by the user
* @see https://doc.xt.com/#websocket_privateorderChange
* @see https://doc.xt.com/#futures_user_websocket_v2order
* @param {string} [symbol] unified market symbol
* @param {int} [since] not used by xt watchOrders
* @param {int} [limit] the maximum number of orders to return
* @param {object} params extra parameters specific to the xt api endpoint
* @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const name = 'order';
let market = undefined;
if (symbol !== undefined) {
market = this.market(symbol);
}
const orders = await this.subscribe(name, 'private', 'watchOrders', market, undefined, params);
if (this.newUpdates) {
limit = orders.getLimit(symbol, limit);
}
return this.filterBySinceLimit(orders, since, limit, 'timestamp');
}
/**
* @method
* @name xt#watchMyTrades
* @description watches information on multiple trades made by the user
* @see https://doc.xt.com/#websocket_privateorderDeal
* @see https://doc.xt.com/#futures_user_websocket_v2trade
* @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 orde structures to retrieve
* @param {object} params extra parameters specific to the kucoin 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 = {}) {
await this.loadMarkets();
const name = 'trade';
let market = undefined;
if (symbol !== undefined) {
market = this.market(symbol);
}
const trades = await this.subscribe(name, 'private', 'watchMyTrades', market, undefined, params);
if (this.newUpdates) {
limit = trades.getLimit(symbol, limit);
}
return this.filterBySinceLimit(trades, since, limit, 'timestamp');
}
/**
* @method
* @name xt#watchOrders
* @description watches information on multiple orders made by the user
* @see https://doc.xt.com/#websocket_privatebalanceChange
* @see https://doc.xt.com/#futures_user_websocket_v2balance
* @param {object} params extra parameters specific to the xt api endpoint
* @returns {object[]} a list of [balance structures]{@link https://docs.ccxt.com/#/?id=balance-structure}
*/
async watchBalance(params = {}) {
await this.loadMarkets();
const name = 'balance';
return await this.subscribe(name, 'private', 'watchBalance', undefined, undefined, params);
}
/**
* @method
* @name xt#watchPositions
* @see https://doc.xt.com/#futures_user_websocket_v2position
* @description watch all open positions
* @param {string[]|undefined} symbols list of unified market symbols
* @param {number} [since] since timestamp
* @param {number} [limit] limit
* @param {object} params extra parameters specific to the exchange API endpoint
* @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
*/
async watchPositions(symbols = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const url = this.urls['api']['ws']['contract'] + '/' + 'user';
const client = this.client(url);
this.setPositionsCache(client);
const fetchPositionsSnapshot = this.handleOption('watchPositions', 'fetchPositionsSnapshot', true);
const awaitPositionsSnapshot = this.handleOption('watchPositions', 'awaitPositionsSnapshot', true);
const cache = this.positions;
if (fetchPositionsSnapshot && awaitPositionsSnapshot && this.isEmpty(cache)) {
const snapshot = await client.future('fetchPositionsSnapshot');
return this.filterBySymbolsSinceLimit(snapshot, symbols, since, limit, true);
}
const name = 'position';
const newPositions = await this.subscribe(name, 'private', 'watchPositions', undefined, undefined, params);
if (this.newUpdates) {
return newPositions;
}
return this.filterBySymbolsSinceLimit(cache, symbols, since, limit, true);
}
setPositionsCache(client) {
if (this.positions === undefined) {
this.positions = new ArrayCacheBySymbolBySide();
}
const fetchPositionsSnapshot = this.handleOption('watchPositions', 'fetchPositionsSnapshot');
if (fetchPositionsSnapshot) {
const messageHash = 'fetchPositionsSnapshot';
if (!(messageHash in client.futures)) {
client.future(messageHash);
this.spawn(this.loadPositionsSnapshot, client, messageHash);
}
}
}
async loadPositionsSnapshot(client, messageHash) {
const positions = await this.fetchPositions(undefined);
this.positions = new ArrayCacheBySymbolBySide();
const cache = this.positions;
for (let i = 0; i < positions.length; i++) {
const position = positions[i];
const contracts = this.safeNumber(position, 'contracts', 0);
if (contracts > 0) {
cache.append(position);
}
}
// don't remove the future from the .futures cache
const future = client.futures[messageHash];
future.resolve(cache);
client.resolve(cache, 'position::contract');
}
handlePosition(client, message) {
//
// {
// topic: 'position',
// event: 'position',
// data: {
// accountId: 245296,
// accountType: 0,
// symbol: 'eth_usdt',
// contractType: 'PERPETUAL',
// positionType: 'CROSSED',
// positionSide: 'LONG',
// positionSize: '1',
// closeOrderSize: '0',
// availableCloseSize: '1',
// realizedProfit: '-0.0121',
// entryPrice: '2637.87',
// openOrderSize: '1',
// isolatedMargin: '2.63787',
// openOrderMarginFrozen: '2.78832014',
// underlyingType: 'U_BASED',
// leverage: 10,
// welfareAccount: false,
// profitFixedLatest: {},
// closeProfit: '0.0000',
// totalFee: '-0.0158',
// totalFundFee: '0.0037',
// markPrice: '2690.96'
// }
// }
//
if (this.positions === undefined) {
this.positions = new ArrayCacheBySymbolBySide();
}
const cache = this.positions;
const data = this.safeDict(message, 'data', {});
const position = this.parsePosition(data);
cache.append(position);
const messageHashes = this.findMessageHashes(client, 'position::contract');
for (let i = 0; i < messageHashes.length; i++) {
const messageHash = messageHashes[i];
const parts = messageHash.split('::');
const symbolsString = parts[1];
const symbols = symbolsString.split(',');
const positions = this.filterByArray([position], 'symbol', symbols, false);
if (!this.isEmpty(positions)) {
client.resolve(positions, messageHash);
}
}
client.resolve([position], 'position::contract');
}
handleTicker(client, message) {
//
// spot
//
// {
// topic: 'ticker',
// event: 'ticker@btc_usdt',
// data: {
// s: 'btc_usdt', // symbol
// t: 1683501935877, // time(Last transaction time)
// cv: '-82.67', // priceChangeValue(24 hour price change)
// cr: '-0.0028', // priceChangeRate 24-hour price change (percentage)
// o: '28823.87', // open price
// c: '28741.20', // close price
// h: '29137.64', // highest price
// l: '28660.93', // lowest price
// q: '6372.601573', // quantity
// v: '184086075.2772391' // volume
// }
// }
//
// contract
//
// {
// "topic": "ticker",
// "event": "ticker@btc_usdt",
// "data": {
// "s": "btc_index", // trading pair
// "o": "49000", // opening price
// "c": "50000", // closing price
// "h": "0.1", // highest price
// "l": "0.1", // lowest price
// "a": "0.1", // volume
// "v": "0.1", // turnover
// "ch": "0.21", // quote change
// "t": 123124124 // timestamp
// }
// }
//
// agg_ticker (contract)
//
// {
// "topic": "agg_ticker",
// "event": "agg_ticker@btc_usdt",
// "data": {
// "s": "btc_index", // trading pair
// "o": "49000", // opening price
// "c": "50000", // closing price
// "h": "0.1", // highest price
// "l": "0.1", // lowest price
// "a": "0.1", // volume
// "v": "0.1", // turnover
// "ch": "0.21", // quote change
// "i": "0.21" , // index price
// "m": "0.21", // mark price
// "bp": "0.21", // bid price
// "ap": "0.21" , // ask price
// "t": 123124124 // timestamp
// }
// }
//
const data = this.safeDict(message, 'data');
const marketId = this.safeString(data, 's');
if (marketId !== undefined) {
const cv = this.safeString(data, 'cv');
const isSpot = cv !== undefined;
const ticker = this.parseTicker(data);
const symbol = ticker['symbol'];
this.tickers[symbol] = ticker;
const event = this.safeString(message, 'event');
const messageHashTail = isSpot ? 'spot' : 'contract';
const messageHash = event + '::' + messageHashTail;
client.resolve(ticker, messageHash);
}
return message;
}
handleTickers(client, message) {
//
// spot
//
// {
// topic: 'tickers',
// event: 'tickers',
// data: [
// {
// s: 'elon_usdt',
// t: 1683502958381,
// cv: '-0.0000000125',
// cr: '-0.0495',
// o: '0.0000002522',
// c: '0.0000002397',
// h: '0.0000002690',
// l: '0.0000002371',
// q: '3803783034.0000000000',
// v: '955.3260820022'
// },
// ...
// ]
// }
//
// contract
//
// {
// "topic": "tickers",
// "event": "tickers",
// "data": [
// {
// "s": "btc_index", // trading pair
// "o": "49000", // opening price
// "c": "50000", // closing price
// "h": "0.1", // highest price
// "l": "0.1", // lowest price
// "a": "0.1", // volume
// "v": "0.1", // turnover
// "ch": "0.21", // quote change
// "t": 123124124 // timestamp
// }
// ]
// }
//
// agg_ticker (contract)
//
// {
// "topic": "agg_tickers",
// "event": "agg_tickers",
// "data": [
// {
// "s": "btc_index", // trading pair
// "o": "49000", // opening price
// "c": "50000", // closing price
// "h": "0.1", // highest price
// "l": "0.1", // lowest price
// "a": "0.1", // volume
// "v": "0.1", // turnover
// "ch": "0.21", // quote change
// "i": "0.21" , // index price
// "m": "0.21", // mark price
// "bp": "0.21", // bid price
// "ap": "0.21" , // ask price
// "t": 123124124 // timestamp
// }
// ]
// }
//
const data = this.safeList(message, 'data', []);
const firstTicker = this.safeDict(data, 0);
const spotTest = this.safeString2(firstTicker, 'cv', 'aq');
const tradeType = (spotTest !== undefined) ? 'spot' : 'contract';
const newTickers = [];
for (let i = 0; i < data.length; i++) {
const tickerData = data[i];
const ticker = this.parseTicker(tickerData);
const symbol = ticker['symbol'];
this.tickers[symbol] = ticker;
newTickers.push(ticker);
}
const messageHashStart = this.safeString(message, 'topic') + '::' + tradeType;
const messageHashes = this.findMessageHashes(client, messageHashStart + '::');
for (let i = 0; i < messageHashes.length; i++) {
const messageHash = messageHashes[i];
const parts = messageHash.split('::');
const symbolsString = parts[2];
const symbols = symbolsString.split(',');
const tickers = this.filterByArray(newTickers, 'symbol', symbols);
const tickersSymbols = Object.keys(tickers);
const numTickers = tickersSymbols.length;
if (numTickers > 0) {
client.resolve(tickers, messageHash);
}
}
client.resolve(this.tickers, messageHashStart);
return message;
}
handleOHLCV(client, message) {
//
// spot
//
// {
// "topic": "kline",
// "event": "kline@btc_usdt,5m",
// "data": {
// "s": "btc_usdt", // symbol
// "t": 1656043200000, // time
// "i": "5m", // interval
// "o": "44000", // open price
// "c": "50000", // close price
// "h": "52000", // highest price
// "l": "36000", // lowest price
// "q": "34.2", // qty(quantity)
// "v": "230000" // volume
// }
// }
//
// contract
//
// {
// "topic": "kline",
// "event": "kline@btc_usdt,5m",
// "data": {
// "s": "btc_index", // trading pair
// "o": "49000", // opening price
// "c": "50000", // closing price
// "h": "0.1", // highest price
// "l": "0.1", // lowest price
// "a": "0.1", // volume
// "v": "0.1", // turnover
// "ch": "0.21", // quote change
// "t": 123124124 // timestamp
// }
// }
//
const data = this.safeDict(message, 'data', {});
const marketId = this.safeString(data, 's');
if (marketId !== undefined) {
const timeframe = this.safeString(data, 'i');
const tradeType = ('q' in data) ? 'spot' : 'contract';
const market = this.safeMarket(marketId, undefined, undefined, tradeType);
const symbol = market['symbol'];
const parsed = this.parseOHLCV(data, market);
this.ohlcvs[symbol] = this.safeDict(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(parsed);
const event = this.safeString(message, 'event');
const messageHash = event + '::' + tradeType;
client.resolve(stored, messageHash);
}
return message;
}
handleTrade(client, message) {
//
// spot
//
// {
// topic: 'trade',
// event: 'trade@btc_usdt',
// data: {
// s: 'btc_usdt',
// i: '228825383103928709',
// t: 1684258222702,
// p: '27003.65',
// q: '0.000796',
// b: true
// }
// }
//
// contract
//
// {
// "topic": "trade",
// "event": "trade@btc_usdt",
// "data": {
// "s": "btc_index", // trading pair
// "p": "50000", // price
// "a": "0.1" // Quantity
// "m": "BID" // Deal side BID:Buy ASK:Sell
// "t": 123124124 // timestamp
// }
// }
//
const data = this.safeDict(message, 'data');
const marketId = this.safeStringLower(data, 's');
if (marketId !== undefined) {
const trade = this.parseTrade(data);
const i = this.safeString(data, 'i');
const tradeType = (i !== undefined) ? 'spot' : 'contract';
const market = this.safeMarket(marketId, undefined, undefined, tradeType);
const symbol = market['symbol'];
const event = this.safeString(message, 'event');
let tradesArray = this.safeValue(this.trades, symbol);
if (tradesArray === undefined) {
const tradesLimit = this.safeInteger(this.options, 'tradesLimit', 1000);
tradesArray = new ArrayCache(tradesLimit);
this.trades[symbol] = tradesArray;
}
tradesArray.append(trade);
const messageHash = event + '::' + tradeType;
client.resolve(tradesArray, messageHash);
}
return message;
}
handleOrderBook(client, message) {
//
// spot
//
// {
// "topic": "depth",
// "event": "depth@btc_usdt,20",
// "data": {
// "s": "btc_usdt", // symbol
// "fi": 1681433733351, // firstUpdateId = previous lastUpdateId + 1
// "i": 1681433733371, // updateId
// "a": [ // asks(sell order)
// [ // [0]price, [1]quantity
// "34000", // price
// "1.2" // quantity
// ],
// [
// "34001",
// "2.3"
// ]
// ],
// "b": [ // bids(buy order)
// [
// "32000",
// "0.2"
// ],
// [
// "31000",
// "0.5"
// ]
// ]
// }
// }
//
// contract
//
// {
// "topic": "depth",
// "event": "depth@btc_usdt,20",
// "data": {
// s: "btc_usdt",
// pu: "548111455664",
// fu: "548111455665",
// u: "548111455667",
// a: [
// [
// "26841.5",
// "50210",
// ],
// ],
// b: [
// [
// "26841",
// "67075",
// ],
// ],
// t: 1684530667083,
// }
// }
//
const data = this.safeDict(message, 'data');
const marketId = this.safeString(data, 's');
if (marketId !== undefined) {
let event = this.safeString(message, 'event');
const splitEvent = event.split(',');
event = this.safeString(splitEvent, 0);
const tradeType = ('fu' in data) ? 'contract' : 'spot';
const market = this.safeMarket(marketId, undefined, undefined, tradeType);
const symbol = market['symbol'];
const obAsks = this.safeList(data, 'a');
const obBids = this.safeList(data, 'b');
const messageHash = event + '::' + tradeType;
if (!(symbol in this.orderbooks)) {
const subscription = this.safeDict(client.subscriptions, messageHash, {});
const limit = this.safeInteger(subscription, 'limit');
this.orderbooks[symbol] = this.orderBook({}, limit);
}
const orderbook = this.orderbooks[symbol];
const nonce = this.safeInteger(orderbook, 'nonce');
if (nonce === undefined) {
const cacheLength = orderbook.cache.length;
const snapshotDelay = this.handleOption('watchOrderBook', 'snapshotDelay', 25);
if (cacheLength === snapshotDelay) {
this.spawn(this.loadOrderBook, client, messageHash, symbol);
}
orderbook.cache.push(data);
return;
}
if (obAsks !== undefined) {
const asks = orderbook['asks'];
for (let i = 0; i < obAsks.length; i++) {
const ask = obAsks[i];
const price = this.safeNumber(ask, 0);
const quantity = this.safeNumber(ask, 1);
asks.store(price, quantity);
}
}
if (obBids !== undefined) {
const bids = orderbook['bids'];
for (let i = 0; i < obBids.length; i++) {
const bid = obBids[i];
const price = this.safeNumber(bid, 0);
const quantity = this.safeNumber(bid, 1);
bids.store(price, quantity);
}
}
const timestamp = this.safeInteger(data, 't');
orderbook['nonce'] = this.safeInteger2(data, 'i', 'u');
orderbook['timestamp'] = timestamp;
orderbook['datetime'] = this.iso8601(timestamp);
orderbook['symbol'] = symbol;
client.resolve(orderbook, messageHash);
}
}
parseWsOrderTrade(trade, market = undefined) {
//
// {
// "s": "btc_usdt", // symbol
// "t": 1656043204763, // time happened time
// "i": "6216559590087220004", // orderId,
// "ci": "test123", // clientOrderId
// "st": "PARTIALLY_FILLED", // state
// "sd": "BUY", // side BUY/SELL
// "eq": "2", // executedQty executed quantity
// "ap": "30000", // avg price
// "f": "0.002" // fee
// }
//
// contract
//
// {
// "symbol": "btc_usdt", // Trading pair
// "orderId": "1234", // Order Id
// "origQty": "34244", // Original Quantity
// "avgPrice": "123", // Quantity
// "price": "1111", // Average price
// "executedQty": "34244", // Volume (Cont)
// "orderSide": "BUY", // BUY, SELL
// "positionSide": "LONG", // LONG, SHORT
// "marginFrozen": "123", // Occupied margin
// "sourceType": "default", // DEFAULT:normal order,ENTRUST:plan commission,PROFIR:Take Profit and Stop Loss
// "sourceId" : "1231231", // Triggering conditions ID
// "state": "", // state:NEW:New order (unfilled);PARTIALLY_FILLED:Partial deal;PARTIALLY_CANCELED:Partial revocation;FILLED:Filled;CANCELED:Cancled;REJECTED:Order failed;EXPIRED:Expired
// "createTime": 1731231231, // CreateTime
// "clientOrderId": "204788317630342726"
// }
//
const marketId = this.safeString(trade, 's');
const tradeType = ('symbol' in trade) ? 'contract' : 'spot';
market = this.safeMarket(marketId, market, undefined, tradeType);
const timestamp = this.safeString(trade, 't');
return this.safeTrade({
'info': trade,
'id': undefined,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'symbol': market['symbol'],
'order': this.safeString(trade, 'i', 'orderId'),
'type': this.parseOrderStatus(this.safeString(trade, 'st', 'state')),
'side': this.safeStringLower(trade, 'sd', 'orderSide'),
'takerOrMaker': undefined,
'price': this.safeNumber(trade, 'price'),
'amount': this.safeString(trade, 'origQty'),
'cost': undefined,
'fee': {
'currency': undefined,
'cost': this.safeNumber(trade, 'f'),
'rate': undefined,
},
}, market);
}
parseWsOrder(order, market = undefined) {
//
// spot
//
// {
// "s": "btc_usdt", // symbol
// "bc": "btc", // base currency
// "qc": "usdt", // quotation currency
// "t": 1656043204763, // happened time
// "ct": 1656043204663, // create time
// "i": "6216559590087220004", // order id,
// "ci": "test123", // client order id
// "st": "PARTIALLY_FILLED", // state NEW/PARTIALLY_FILLED/FILLED/CANCELED/REJECTED/EXPIRED
// "sd": "BUY", // side BUY/SELL
// "tp": "LIMIT", // type LIMIT/MARKET
// "oq": "4" // original quantity
// "oqq": 48000, // original quotation quantity
// "eq": "2", // executed quantity
// "lq": "2", // remaining quantity
// "p": "4000", // price
// "ap": "30000", // avg price
// "f":"0.002" // fee
// }
//
// contract
//
// {
// "symbol": "btc_usdt", // Trading pair
// "orderId": "1234", // Order Id
// "origQty": "34244", // Original Quantity
// "avgPrice": "123", // Quantity
// "price": "1111", // Average price
// "executedQty": "34244", // Volume (Cont)
// "orderSide": "BUY", // BUY, SELL
// "positionSide": "LONG", // LONG, SHORT
// "marginFrozen": "123", // Occupied margin
// "sourceType": "default", // DEFAULT:normal order,ENTRUST:plan commission,PROFIR:Take Profit and Stop Loss
// "sourceId" : "1231231", // Triggering conditions ID
// "state": "", // state:NEW:New order (unfilled);PARTIALLY_FILLED:Partial deal;PARTIALLY_CANCELED:Partial revocation;FILLED:Filled;CANCELED:Cancled;REJECTED:Order failed;EXPIRED:Expired
// "createTime": 1731231231, // CreateTime
// "clientOrderId": "204788317630342726"
// }
//
const marketId = this.safeString2(order, 's', 'symbol');
const tradeType = ('symbol' in order) ? 'contract' : 'spot';
market = this.safeMarket(marketId, market, undefined, tradeType);
const timestamp = this.safeInteger2(order, 'ct', 'createTime');
return this.safeOrder({
'info': order,
'id': this.safeString2(order, 'i', 'orderId'),
'clientOrderId': this.safeString2(order, 'ci', 'clientOrderId'),
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'lastTradeTimestamp': undefined,
'symbol': market['symbol'],
'type': market['type'],
'timeInForce': undefined,
'postOnly': undefined,
'side': this.safeStringLower2(order, 'sd', 'orderSide'),
'price': this.safeNumber2(order, 'p', 'price'),
'stopPrice': undefined,
'stopLoss': undefined,
'takeProfit': undefined,
'amount': this.safeString2(order, 'oq', 'origQty'),
'filled': this.safeString2(order, 'eq', 'executedQty'),
'remaining': this.safeString(order, 'lq'),
'cost': undefined,
'average': this.safeString2(order, 'ap', 'avgPrice'),
'status': this.parseOrderStatus(this.safeString(order, 'st', 'state')),
'fee': {
'currency': undefined,
'cost': this.safeNumber(order, 'f'),
},
'trades': undefined,
}, market);
}
handleOrder(client, message) {
//
// spot
//
// {
// "topic": "order",
// "event": "order",
// "data": {
// "s": "btc_usdt", // symbol
// "t": 1656043204763, // time happened time
// "i": "6216559590087220004", // orderId,
// "ci": "test123", // clientOrderId
// "st": "PARTIALLY_FILLED", // state
// "sd": "BUY", // side BUY/SELL
// "eq": "2", // executedQty executed quantity
// "ap": "30000", // avg price
// "f": "0.002" // fee
// }
// }
//
// contract
//
// {
// "topic": "order",
// "event": "order@123456",
// "data": {
// "symbol": "btc_usdt", // Trading pair
// "orderId": "1234", // Order Id
// "origQty": "34244", // Original Quantity
// "avgPrice": "123", // Quantity
// "price": "1111", // Average price
// "executedQty": "34244", // Volume (Cont)
// "orderSide": "BUY", // BUY, SELL
// "positionSide": "LONG", // LONG, SHORT
// "marginFrozen": "123", // Occupied margin
// "sourceType": "default", // DEFAULT:normal order,ENTRUST:plan commission,PROFIR:Take Profit and Stop Loss
// "sourceId" : "1231231", // Triggering conditions ID
// "state": "", // state:NEW:New order (unfilled);PARTIALLY_FILLED:Partial deal;PARTIALLY_CANCELED:Partial revocation;FILLED:Filled;CANCELED:Cancled;REJECTED:Order failed;EXPIRED:Expired
// "createTime": 1731231231, // CreateTime
// "clientOrderId": "204788317630342726"
// }
// }
//
let orders = this.orders;
if (o