sfccxt
Version:
A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges
1,119 lines (1,087 loc) • 45.9 kB
JavaScript
'use strict';
// ---------------------------------------------------------------------------
const phemexRest = require ('../phemex.js');
const Precise = require ('../base/Precise');
const { ArrayCache, ArrayCacheByTimestamp, ArrayCacheBySymbolById } = require ('./base/Cache');
// ---------------------------------------------------------------------------
module.exports = class phemex extends phemexRest {
describe () {
return this.deepExtend (super.describe (), {
'has': {
'ws': true,
'watchTicker': true,
'watchTickers': false, // for now
'watchTrades': true,
'watchMyTrades': true,
'watchOrders': true,
'watchOrderBook': true,
'watchOHLCV': true,
},
'urls': {
'test': {
'ws': 'wss://testnet.phemex.com/ws',
},
'api': {
'ws': 'wss://phemex.com/ws',
},
},
'options': {
'tradesLimit': 1000,
'OHLCVLimit': 1000,
},
'streaming': {
'keepAlive': 20000,
},
});
}
fromEn (en, scale) {
if (en === undefined) {
return undefined;
}
const precise = new Precise (en);
precise.decimals = this.sum (precise.decimals, scale);
precise.reduce ();
return precise.toString ();
}
fromEp (ep, market = undefined) {
if ((ep === undefined) || (market === undefined)) {
return ep;
}
return this.fromEn (ep, this.safeInteger (market, 'priceScale'));
}
fromEv (ev, market = undefined) {
if ((ev === undefined) || (market === undefined)) {
return ev;
}
return this.fromEn (ev, this.safeInteger (market, 'valueScale'));
}
fromEr (er, market = undefined) {
if ((er === undefined) || (market === undefined)) {
return er;
}
return this.fromEn (er, this.safeInteger (market, 'ratioScale'));
}
requestId () {
const requestId = this.sum (this.safeInteger (this.options, 'requestId', 0), 1);
this.options['requestId'] = requestId;
return requestId;
}
parseSwapTicker (ticker, market = undefined) {
//
// {
// close: 442800,
// fundingRate: 10000,
// high: 445400,
// indexPrice: 442621,
// low: 428400,
// markPrice: 442659,
// open: 432200,
// openInterest: 744183,
// predFundingRate: 10000,
// symbol: 'LTCUSD',
// turnover: 8133238294,
// volume: 934292
// }
//
const marketId = this.safeString (ticker, 'symbol');
market = this.safeMarket (marketId, market);
const symbol = market['symbol'];
const timestamp = this.safeIntegerProduct (ticker, 'timestamp', 0.000001);
const lastString = this.fromEp (this.safeString (ticker, 'close'), market);
const last = this.parseNumber (lastString);
const quoteVolume = this.parseNumber (this.fromEv (this.safeString (ticker, 'turnover'), market));
const baseVolume = this.parseNumber (this.fromEv (this.safeString (ticker, 'volume'), market));
let change = undefined;
let percentage = undefined;
let average = undefined;
const openString = this.omitZero (this.fromEp (this.safeString (ticker, 'open'), market));
const open = this.parseNumber (openString);
if ((openString !== undefined) && (lastString !== undefined)) {
change = this.parseNumber (Precise.stringSub (lastString, openString));
average = this.parseNumber (Precise.stringDiv (Precise.stringAdd (lastString, openString), '2'));
percentage = this.parseNumber (Precise.stringMul (Precise.stringSub (Precise.stringDiv (lastString, openString), '1'), '100'));
}
const result = {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': this.parseNumber (this.fromEp (this.safeString (ticker, 'high'), market)),
'low': this.parseNumber (this.fromEp (this.safeString (ticker, 'low'), market)),
'bid': undefined,
'bidVolume': undefined,
'ask': undefined,
'askVolume': undefined,
'vwap': undefined,
'open': open,
'close': last,
'last': last,
'previousClose': undefined, // previous day close
'change': change,
'percentage': percentage,
'average': average,
'baseVolume': baseVolume,
'quoteVolume': quoteVolume,
'info': ticker,
};
return result;
}
handleTicker (client, message) {
//
// {
// spot_market24h: {
// askEp: 958148000000,
// bidEp: 957884000000,
// highEp: 962000000000,
// lastEp: 958220000000,
// lowEp: 928049000000,
// openEp: 935597000000,
// symbol: 'sBTCUSDT',
// turnoverEv: 146074214388978,
// volumeEv: 15492228900
// },
// timestamp: 1592847265888272100
// }
//
// swap
//
// {
// market24h: {
// close: 442800,
// fundingRate: 10000,
// high: 445400,
// indexPrice: 442621,
// low: 428400,
// markPrice: 442659,
// open: 432200,
// openInterest: 744183,
// predFundingRate: 10000,
// symbol: 'LTCUSD',
// turnover: 8133238294,
// volume: 934292
// },
// timestamp: 1592845585373374500
// }
//
let name = 'market24h';
let ticker = this.safeValue (message, name);
let result = undefined;
if (ticker === undefined) {
name = 'spot_market24h';
ticker = this.safeValue (message, name);
result = this.parseTicker (ticker);
} else {
result = this.parseSwapTicker (ticker);
}
const symbol = result['symbol'];
const messageHash = name + ':' + symbol;
const timestamp = this.safeIntegerProduct (message, 'timestamp', 0.000001);
result['timestamp'] = timestamp;
result['datetime'] = this.iso8601 (timestamp);
this.tickers[symbol] = result;
client.resolve (result, messageHash);
}
async watchBalance (params = {}) {
/**
* @method
* @name phemex#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 phemex api endpoint
* @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure}
*/
await this.loadMarkets ();
const [ type, query ] = this.handleMarketTypeAndParams ('watchBalance', undefined, params);
const messageHash = type + ':balance';
return await this.subscribePrivate (type, messageHash, query);
}
handleBalance (type, client, message) {
// spot
// [
// {
// balanceEv: 0,
// currency: 'BTC',
// lastUpdateTimeNs: '1650442638722099092',
// lockedTradingBalanceEv: 0,
// lockedWithdrawEv: 0,
// userID: 2647224
// },
// {
// balanceEv: 1154232337,
// currency: 'USDT',
// lastUpdateTimeNs: '1650442617610017597',
// lockedTradingBalanceEv: 0,
// lockedWithdrawEv: 0,
// userID: 2647224
// }
// ]
//
// swap
// [
// {
// accountBalanceEv: 0,
// accountID: 26472240001,
// bonusBalanceEv: 0,
// currency: 'BTC',
// totalUsedBalanceEv: 0,
// userID: 2647224
// }
// ]
//
for (let i = 0; i < message.length; i++) {
const balance = message[i];
const currencyId = this.safeString (balance, 'currency');
const code = this.safeCurrencyCode (currencyId);
const currency = this.safeValue (this.currencies, code, {});
const scale = this.safeInteger (currency, 'valueScale', 8);
const account = this.account ();
let usedEv = this.safeString (balance, 'totalUsedBalanceEv');
if (usedEv === undefined) {
const lockedTradingBalanceEv = this.safeString (balance, 'lockedTradingBalanceEv');
const lockedWithdrawEv = this.safeString (balance, 'lockedWithdrawEv');
usedEv = Precise.stringAdd (lockedTradingBalanceEv, lockedWithdrawEv);
}
const totalEv = this.safeString2 (balance, 'accountBalanceEv', 'balanceEv');
account['used'] = this.fromEn (usedEv, scale);
account['total'] = this.fromEn (totalEv, scale);
this.balance[code] = account;
this.balance = this.safeBalance (this.balance);
}
const messageHash = type + ':balance';
client.resolve (this.balance, messageHash);
}
handleTrades (client, message) {
//
// {
// sequence: 1795484727,
// symbol: 'sBTCUSDT',
// trades: [
// [ 1592891002064516600, 'Buy', 964020000000, 1431000 ],
// [ 1592890978987934500, 'Sell', 963704000000, 1401800 ],
// [ 1592890972918701800, 'Buy', 963938000000, 2018600 ],
// ],
// type: 'snapshot'
// }
//
const name = 'trade';
const marketId = this.safeString (message, 'symbol');
const market = this.safeMarket (marketId);
const symbol = market['symbol'];
const messageHash = name + ':' + 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 trades = this.safeValue (message, 'trades', []);
const parsed = this.parseTrades (trades, market);
for (let i = 0; i < parsed.length; i++) {
stored.append (parsed[i]);
}
client.resolve (stored, messageHash);
}
handleOHLCV (client, message) {
//
// {
// kline: [
// [ 1592905200, 60, 960688000000, 960709000000, 960709000000, 960400000000, 960400000000, 848100, 8146756046 ],
// [ 1592905140, 60, 960718000000, 960716000000, 960717000000, 960560000000, 960688000000, 4284900, 41163743512 ],
// [ 1592905080, 60, 960513000000, 960684000000, 960718000000, 960684000000, 960718000000, 4880500, 46887494349 ],
// ],
// sequence: 1804401474,
// symbol: 'sBTCUSDT',
// type: 'snapshot'
// }
//
const name = 'kline';
const marketId = this.safeString (message, 'symbol');
const market = this.safeMarket (marketId);
const symbol = market['symbol'];
const candles = this.safeValue (message, name, []);
const first = this.safeValue (candles, 0, []);
const interval = this.safeString (first, 1);
const timeframe = this.findTimeframe (interval);
if (timeframe !== undefined) {
const messageHash = name + ':' + timeframe + ':' + symbol;
const ohlcvs = this.parseOHLCVs (candles, market);
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;
}
for (let i = 0; i < ohlcvs.length; i++) {
const candle = ohlcvs[i];
stored.append (candle);
}
client.resolve (stored, messageHash);
}
}
async watchTicker (symbol, params = {}) {
/**
* @method
* @name phemex#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 phemex 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);
symbol = market['symbol'];
const name = market['spot'] ? 'spot_market24h' : 'market24h';
const url = this.urls['api']['ws'];
const requestId = this.requestId ();
const subscriptionHash = name + '.subscribe';
const messageHash = name + ':' + symbol;
const subscribe = {
'method': subscriptionHash,
'id': requestId,
'params': [],
};
const request = this.deepExtend (subscribe, params);
return await this.watch (url, messageHash, request, subscriptionHash);
}
async watchTrades (symbol, since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name phemex#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 phemex 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 requestId = this.requestId ();
const name = 'trade';
const messageHash = name + ':' + symbol;
const method = name + '.subscribe';
const subscribe = {
'method': method,
'id': requestId,
'params': [
market['id'],
],
};
const request = this.deepExtend (subscribe, params);
const trades = await this.watch (url, messageHash, request, messageHash);
if (this.newUpdates) {
limit = trades.getLimit (symbol, limit);
}
return this.filterBySinceLimit (trades, since, limit, 'timestamp', true);
}
async watchOrderBook (symbol, limit = undefined, params = {}) {
/**
* @method
* @name phemex#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 phemex 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 url = this.urls['api']['ws'];
const requestId = this.requestId ();
const name = 'orderbook';
const messageHash = name + ':' + symbol;
const method = name + '.subscribe';
const subscribe = {
'method': method,
'id': requestId,
'params': [
market['id'],
],
};
const request = this.deepExtend (subscribe, params);
const orderbook = await this.watch (url, messageHash, request, messageHash);
return orderbook.limit ();
}
async watchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name phemex#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 phemex 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 url = this.urls['api']['ws'];
const requestId = this.requestId ();
const name = 'kline';
const messageHash = name + ':' + timeframe + ':' + symbol;
const method = name + '.subscribe';
const subscribe = {
'method': method,
'id': requestId,
'params': [
market['id'],
this.safeInteger (this.timeframes, timeframe),
],
};
const request = this.deepExtend (subscribe, params);
const ohlcv = await this.watch (url, messageHash, request, messageHash);
if (this.newUpdates) {
limit = ohlcv.getLimit (symbol, limit);
}
return this.filterBySinceLimit (ohlcv, since, limit, 0, true);
}
handleDelta (bookside, delta, market = undefined) {
const bidAsk = this.parseBidAsk (delta, 0, 1, market);
bookside.storeArray (bidAsk);
}
handleDeltas (bookside, deltas, market = undefined) {
for (let i = 0; i < deltas.length; i++) {
this.handleDelta (bookside, deltas[i], market);
}
}
handleOrderBook (client, message) {
//
// {
// book: {
// asks: [
// [ 960316000000, 6993800 ],
// [ 960318000000, 13183000 ],
// [ 960319000000, 9170200 ],
// ],
// bids: [
// [ 959941000000, 8385300 ],
// [ 959939000000, 10296600 ],
// [ 959930000000, 3672400 ],
// ]
// },
// depth: 30,
// sequence: 1805784701,
// symbol: 'sBTCUSDT',
// timestamp: 1592908460404461600,
// type: 'snapshot'
// }
//
const marketId = this.safeString (message, 'symbol');
const market = this.safeMarket (marketId);
const symbol = market['symbol'];
const type = this.safeString (message, 'type');
const depth = this.safeInteger (message, 'depth');
const name = 'orderbook';
const messageHash = name + ':' + symbol;
const nonce = this.safeInteger (message, 'sequence');
const timestamp = this.safeIntegerProduct (message, 'timestamp', 0.000001);
if (type === 'snapshot') {
const book = this.safeValue (message, 'book', {});
const snapshot = this.parseOrderBook (book, symbol, timestamp, 'bids', 'asks', 0, 1, market);
snapshot['nonce'] = nonce;
const orderbook = this.orderBook (snapshot, depth);
this.orderbooks[symbol] = orderbook;
client.resolve (orderbook, messageHash);
} else {
const orderbook = this.safeValue (this.orderbooks, symbol);
if (orderbook !== undefined) {
const changes = this.safeValue (message, 'book', {});
const asks = this.safeValue (changes, 'asks', []);
const bids = this.safeValue (changes, 'bids', []);
this.handleDeltas (orderbook['asks'], asks, market);
this.handleDeltas (orderbook['bids'], bids, market);
orderbook['nonce'] = nonce;
orderbook['timestamp'] = timestamp;
orderbook['datetime'] = this.iso8601 (timestamp);
this.orderbooks[symbol] = orderbook;
client.resolve (orderbook, messageHash);
}
}
}
async watchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name phemex#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 phemex api endpoint
* @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure
*/
await this.loadMarkets ();
let messageHash = 'trades';
let market = undefined;
let type = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
symbol = market['symbol'];
messageHash = messageHash + ':' + market['symbol'];
}
[ type, params ] = this.handleMarketTypeAndParams ('watchMyTrades', market, params);
if (symbol === undefined) {
messageHash = messageHash + ':' + type;
}
const trades = await this.subscribePrivate (type, messageHash, params);
if (this.newUpdates) {
limit = trades.getLimit (symbol, limit);
}
return this.filterBySymbolSinceLimit (trades, symbol, since, limit, true);
}
handleMyTrades (client, message) {
//
// [
// {
// "avgPriceEp":4138763000000,
// "baseCurrency":"BTC",
// "baseQtyEv":0,
// "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2",
// "execBaseQtyEv":30100,
// "execFeeEv":31,
// "execID":"d3b10cfa-84e3-5752-828e-78a79617e598",
// "execPriceEp":4138763000000,
// "execQuoteQtyEv":1245767663,
// "feeCurrency":"BTC",
// "lastLiquidityInd":"RemovedLiquidity",
// "ordType":"Market",
// "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195",
// "priceEp":4549022000000,
// "qtyType":"ByQuote",
// "quoteCurrency":"USDT",
// "quoteQtyEv":1248000000,
// "side":"Buy",
// "symbol":"sBTCUSDT",
// "tradeType":"Trade",
// "transactTimeNs":"1650442617609928764",
// "userID":2647224
// }
// ]
//
const channel = 'trades';
const tradesLength = message.length;
if (tradesLength === 0) {
return;
}
let cachedTrades = this.myTrades;
if (cachedTrades === undefined) {
const limit = this.safeInteger (this.options, 'tradesLimit', 1000);
cachedTrades = new ArrayCacheBySymbolById (limit);
}
const marketIds = {};
let type = undefined;
for (let i = 0; i < message.length; i++) {
const rawTrade = message[i];
const marketId = this.safeString (rawTrade, 'symbol');
// skip delisted markets
if (marketId in this.markets_by_id) {
const parsed = this.parseTrade (rawTrade);
cachedTrades.append (parsed);
const symbol = parsed['symbol'];
const market = this.market (symbol);
if (type === undefined) {
type = market['type'];
}
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);
}
// generic subscription
const messageHash = channel + ':' + type;
client.resolve (cachedTrades, messageHash);
}
async watchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name phemex#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 phemex api endpoint
* @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
await this.loadMarkets ();
let messageHash = 'orders';
let market = undefined;
let type = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
symbol = market['symbol'];
messageHash = messageHash + ':' + market['symbol'];
}
[ type, params ] = this.handleMarketTypeAndParams ('watchOrders', market, params);
if (symbol === undefined) {
messageHash = messageHash + ':' + type;
}
const orders = await this.subscribePrivate (type, messageHash, params);
if (this.newUpdates) {
limit = orders.getLimit (symbol, limit);
}
return this.filterBySymbolSinceLimit (orders, symbol, since, limit, true);
}
handleOrders (client, message) {
// spot update
// {
// "closed":[
// {
// "action":"New",
// "avgPriceEp":4138763000000,
// "baseCurrency":"BTC",
// "baseQtyEv":0,
// "bizError":0,
// "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2",
// "createTimeNs":"1650442617606017583",
// "cumBaseQtyEv":30100,
// "cumFeeEv":31,
// "cumQuoteQtyEv":1245767663,
// "cxlRejReason":0,
// "feeCurrency":"BTC",
// "leavesBaseQtyEv":0,
// "leavesQuoteQtyEv":0,
// "ordStatus":"Filled",
// "ordType":"Market",
// "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195",
// "pegOffsetValueEp":0,
// "priceEp":4549022000000,
// "qtyType":"ByQuote",
// "quoteCurrency":"USDT",
// "quoteQtyEv":1248000000,
// "side":"Buy",
// "stopPxEp":0,
// "symbol":"sBTCUSDT",
// "timeInForce":"ImmediateOrCancel",
// "tradeType":"Trade",
// "transactTimeNs":"1650442617609928764",
// "triggerTimeNs":0,
// "userID":2647224
// }
// ],
// "fills":[
// {
// "avgPriceEp":4138763000000,
// "baseCurrency":"BTC",
// "baseQtyEv":0,
// "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2",
// "execBaseQtyEv":30100,
// "execFeeEv":31,
// "execID":"d3b10cfa-84e3-5752-828e-78a79617e598",
// "execPriceEp":4138763000000,
// "execQuoteQtyEv":1245767663,
// "feeCurrency":"BTC",
// "lastLiquidityInd":"RemovedLiquidity",
// "ordType":"Market",
// "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195",
// "priceEp":4549022000000,
// "qtyType":"ByQuote",
// "quoteCurrency":"USDT",
// "quoteQtyEv":1248000000,
// "side":"Buy",
// "symbol":"sBTCUSDT",
// "tradeType":"Trade",
// "transactTimeNs":"1650442617609928764",
// "userID":2647224
// }
// ],
// "open":[
// {
// "action":"New",
// "avgPriceEp":0,
// "baseCurrency":"LTC",
// "baseQtyEv":0,
// "bizError":0,
// "clOrdID":"2c0e5eb5-efb7-60d3-2e5f-df175df412ef",
// "createTimeNs":"1650446670073853755",
// "cumBaseQtyEv":0,
// "cumFeeEv":0,
// "cumQuoteQtyEv":0,
// "cxlRejReason":0,
// "feeCurrency":"LTC",
// "leavesBaseQtyEv":0,
// "leavesQuoteQtyEv":1000000000,
// "ordStatus":"New",
// "ordType":"Limit",
// "orderID":"d2aad92f-50f5-441a-957b-8184b146e3fb",
// "pegOffsetValueEp":0,
// "priceEp":5000000000,
// "qtyType":"ByQuote",
// "quoteCurrency":"USDT",
// "quoteQtyEv":1000000000,
// "side":"Buy",
// }
// ]
// },
//
let trades = [];
const parsedOrders = [];
if (('closed' in message) || ('fills' in message) || ('open' in message)) {
const closed = this.safeValue (message, 'closed', []);
const open = this.safeValue (message, 'open', []);
const orders = this.arrayConcat (open, closed);
const ordersLength = orders.length;
if (ordersLength === 0) {
return;
}
const fills = this.safeValue (message, 'fills', []);
trades = fills;
for (let i = 0; i < orders.length; i++) {
const rawOrder = orders[i];
const marketId = this.safeString (rawOrder, 'symbol');
// skip delisted spot markets
if (marketId in this.markets_by_id) {
const parsedOrder = this.parseOrder (rawOrder);
parsedOrders.push (parsedOrder);
}
}
} else {
for (let i = 0; i < message.length; i++) {
const update = message[i];
const marketId = this.safeString (update, 'symbol');
if (marketId in this.markets_by_id) {
// skip delisted swap markets
const action = this.safeString (update, 'action');
if ((action !== undefined) && (action !== 'Cancel')) {
// order + trade info together
trades.push (update);
}
const parsedOrder = this.parseWSSwapOrder (update);
parsedOrders.push (parsedOrder);
}
}
}
this.handleMyTrades (client, trades);
const limit = this.safeInteger (this.options, 'ordersLimit', 1000);
const marketIds = {};
if (this.orders === undefined) {
this.orders = new ArrayCacheBySymbolById (limit);
}
let type = undefined;
const stored = this.orders;
for (let i = 0; i < parsedOrders.length; i++) {
const parsed = parsedOrders[i];
stored.append (parsed);
const symbol = parsed['symbol'];
const market = this.market (symbol);
if (type === undefined) {
type = market['type'];
}
marketIds[symbol] = true;
}
const keys = Object.keys (marketIds);
for (let i = 0; i < keys.length; i++) {
const messageHash = 'orders' + ':' + keys[i];
client.resolve (this.orders, messageHash);
}
// resolve generic subscription (spot or swap)
const messageHash = 'orders:' + type;
client.resolve (this.orders, messageHash);
}
parseWSSwapOrder (order, market = undefined) {
//
// {
// "accountID":26472240002,
// "action":"Cancel",
// "actionBy":"ByUser",
// "actionTimeNs":"1650450096104760797",
// "addedSeq":26975849309,
// "bonusChangedAmountEv":0,
// "clOrdID":"d9675963-5e4e-6fc8-898a-ec8b934c1c61",
// "closedPnlEv":0,
// "closedSize":0,
// "code":0,
// "cumQty":0,
// "cumValueEv":0,
// "curAccBalanceEv":400079,
// "curAssignedPosBalanceEv":0,
// "curBonusBalanceEv":0,
// "curLeverageEr":0,
// "curPosSide":"None",
// "curPosSize":0,
// "curPosTerm":1,
// "curPosValueEv":0,
// "curRiskLimitEv":5000000000,
// "currency":"USD",
// "cxlRejReason":0,
// "displayQty":0,
// "execFeeEv":0,
// "execID":"00000000-0000-0000-0000-000000000000",
// "execPriceEp":0,
// "execQty":1,
// "execSeq":26975862338,
// "execStatus":"Canceled",
// "execValueEv":0,
// "feeRateEr":0,
// "leavesQty":0,
// "leavesValueEv":0,
// "message":"No error",
// "ordStatus":"Canceled",
// "ordType":"Limit",
// "orderID":"8141deb9-8f94-48f6-9421-a4e3a791537b",
// "orderQty":1,
// "pegOffsetValueEp":0,
// "priceEp":9521,
// "relatedPosTerm":1,
// "relatedReqNum":4,
// "side":"Buy",
// "slTrigger":"ByMarkPrice",
// "stopLossEp":0,
// "stopPxEp":0,
// "symbol":"ADAUSD",
// "takeProfitEp":0,
// "timeInForce":"GoodTillCancel",
// "tpTrigger":"ByLastPrice",
// "transactTimeNs":"1650450096108143014",
// "userID":2647224
// }
//
const id = this.safeString (order, 'orderID');
let clientOrderId = this.safeString (order, 'clOrdID');
if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) {
clientOrderId = undefined;
}
const marketId = this.safeString (order, 'symbol');
market = this.safeMarket (marketId, market);
const symbol = market['symbol'];
const status = this.parseOrderStatus (this.safeString (order, 'ordStatus'));
const side = this.safeStringLower (order, 'side');
const type = this.parseOrderType (this.safeString (order, 'ordType'));
const price = this.parseNumber (this.fromEp (this.safeString (order, 'priceEp'), market));
const amount = this.safeString (order, 'orderQty');
const filled = this.safeString (order, 'cumQty');
const remaining = this.safeString (order, 'leavesQty');
const timestamp = this.safeIntegerProduct (order, 'actionTimeNs', 0.000001);
const costEv = this.safeString (order, 'cumValueEv');
const cost = this.fromEv (costEv, market);
let lastTradeTimestamp = this.safeIntegerProduct (order, 'transactTimeNs', 0.000001);
if (lastTradeTimestamp === 0) {
lastTradeTimestamp = undefined;
}
const timeInForce = this.parseTimeInForce (this.safeString (order, 'timeInForce'));
const stopPrice = this.safeString (order, 'stopPx');
const postOnly = (timeInForce === 'PO');
return this.safeOrder ({
'info': order,
'id': id,
'clientOrderId': clientOrderId,
'datetime': this.iso8601 (timestamp),
'timestamp': timestamp,
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': timeInForce,
'postOnly': postOnly,
'side': side,
'price': price,
'stopPrice': stopPrice,
'amount': amount,
'filled': filled,
'remaining': remaining,
'cost': cost,
'average': undefined,
'status': status,
'fee': undefined,
'trades': undefined,
}, market);
}
handleMessage (client, message) {
// private spot update
// {
// orders: { closed: [ ], fills: [ ], open: [] },
// sequence: 40435835,
// timestamp: '1650443245600839241',
// type: 'snapshot',
// wallets: [
// {
// balanceEv: 0,
// currency: 'BTC',
// lastUpdateTimeNs: '1650442638722099092',
// lockedTradingBalanceEv: 0,
// lockedWithdrawEv: 0,
// userID: 2647224
// },
// {
// balanceEv: 1154232337,
// currency: 'USDT',
// lastUpdateTimeNs: '1650442617610017597',
// lockedTradingBalanceEv: 0,
// lockedWithdrawEv: 0,
// userID: 2647224
// }
// ]
// }
// private swap update
// {
// sequence: 83839628,
// timestamp: '1650382581827447829',
// type: 'snapshot',
// accounts: [
// {
// accountBalanceEv: 0,
// accountID: 26472240001,
// bonusBalanceEv: 0,
// currency: 'BTC',
// totalUsedBalanceEv: 0,
// userID: 2647224
// }
// ],
// orders: [],
// positions: [
// {
// accountID: 26472240001,
// assignedPosBalanceEv: 0,
// avgEntryPriceEp: 0,
// bankruptCommEv: 0,
// bankruptPriceEp: 0,
// buyLeavesQty: 0,
// buyLeavesValueEv: 0,
// buyValueToCostEr: 1150750,
// createdAtNs: 0,
// crossSharedBalanceEv: 0,
// cumClosedPnlEv: 0,
// cumFundingFeeEv: 0,
// cumTransactFeeEv: 0,
// curTermRealisedPnlEv: 0,
// currency: 'BTC',
// dataVer: 2,
// deleveragePercentileEr: 0,
// displayLeverageEr: 10000000000,
// estimatedOrdLossEv: 0,
// execSeq: 0,
// freeCostEv: 0,
// freeQty: 0,
// initMarginReqEr: 1000000,
// lastFundingTime: '1640601827712091793',
// lastTermEndTime: 0,
// leverageEr: 0,
// liquidationPriceEp: 0,
// maintMarginReqEr: 500000,
// makerFeeRateEr: 0,
// markPriceEp: 507806777,
// orderCostEv: 0,
// posCostEv: 0,
// positionMarginEv: 0,
// positionStatus: 'Normal',
// riskLimitEv: 10000000000,
// sellLeavesQty: 0,
// sellLeavesValueEv: 0,
// sellValueToCostEr: 1149250,
// side: 'None',
// size: 0,
// symbol: 'BTCUSD',
// takerFeeRateEr: 0,
// term: 1,
// transactTimeNs: 0,
// unrealisedPnlEv: 0,
// updatedAtNs: 0,
// usedBalanceEv: 0,
// userID: 2647224,
// valueEv: 0
// }
// ]
// }
const id = this.safeInteger (message, 'id');
if (id !== undefined) {
// not every method stores its subscription
// as an object so we can't do indeById here
const subs = client.subscriptions;
const values = Object.values (subs);
for (let i = 0; i < values.length; i++) {
const subscription = values[i];
if (subscription !== true) {
const subId = this.safeInteger (subscription, 'id');
if ((subId !== undefined) && (subId === id)) {
const method = this.safeValue (subscription, 'method');
if (method !== undefined) {
method.call (this, client, message);
return;
}
}
}
}
}
if (('market24h' in message) || ('spot_market24h' in message)) {
return this.handleTicker (client, message);
} else if ('trades' in message) {
return this.handleTrades (client, message);
} else if ('kline' in message) {
return this.handleOHLCV (client, message);
} else if ('book' in message) {
return this.handleOrderBook (client, message);
}
if ('orders' in message) {
const orders = this.safeValue (message, 'orders', {});
this.handleOrders (client, orders);
}
if (('accounts' in message) || ('wallets' in message)) {
const type = ('accounts' in message) ? 'swap' : 'spot';
const accounts = this.safeValue2 (message, 'accounts', 'wallets', []);
this.handleBalance (type, client, accounts);
}
}
handleAuthenticate (client, message) {
//
// {
// "error": null,
// "id": 1234,
// "result": {
// "status": "success"
// }
// }
//
const future = client.futures['authenticated'];
future.resolve (1);
return message;
}
async subscribePrivate (type, messageHash, params = {}) {
await this.loadMarkets ();
await this.authenticate ();
const url = this.urls['api']['ws'];
const requestId = this.seconds ();
const channel = (type === 'spot') ? 'wo.subscribe' : 'aop.subscribe';
let request = {
'id': requestId,
'method': channel,
'params': [],
};
request = this.extend (request, params);
const subscription = {
'id': requestId,
'messageHash': messageHash,
};
return await this.watch (url, messageHash, request, channel, subscription);
}
async authenticate (params = {}) {
this.checkRequiredCredentials ();
const url = this.urls['api']['ws'];
const client = this.client (url);
const time = this.seconds ();
const messageHash = 'authenticated';
const future = client.future (messageHash);
const authenticated = this.safeValue (client.subscriptions, messageHash);
if (authenticated === undefined) {
const expiryDelta = this.safeInteger (this.options, 'expires', 120);
const expiration = this.seconds () + expiryDelta;
const payload = this.apiKey + expiration.toString ();
const signature = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256');
const request = {
'method': 'user.auth',
'params': [ 'API', this.apiKey, signature, expiration ],
'id': time,
};
const subscription = {
'id': time,
'method': this.handleAuthenticate,
};
this.spawn (this.watch, url, messageHash, request, messageHash, subscription);
}
return await future;
}
};