UNPKG

tardis-dev

Version:

Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js

260 lines 9.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PhemexDerivativeTickerMapper = exports.phemexBookChangeMapper = exports.phemexTradesMapper = void 0; const mapper_1 = require("./mapper"); // phemex provides timestamps in nanoseconds const fromNanoSecondsToDate = (nanos) => { const microtimestamp = Math.floor(nanos / 1000); const timestamp = new Date(microtimestamp / 1000); timestamp.μs = microtimestamp % 1000; return timestamp; }; function getPriceScale(symbol) { if (symbol.startsWith('s')) { return 1e8; } return 1e4; } function getQtyScale(symbol) { if (symbol.startsWith('s')) { return 1e8; } return 1; } const COINS_STARTING_WITH_S = ['SOL', 'SUSHI', 'SNX', 'SAND', 'SRM', 'SKLU', 'SXP', 'STORJ', 'SFP', 'STG']; function getInstrumentType(symbol) { if (/\d+$/.test(symbol)) { return 'future'; } if (COINS_STARTING_WITH_S.some((c) => symbol.startsWith(c)) || symbol.startsWith('S') === false) { return 'perpetual'; } return 'spot'; } function getApiSymbolId(symbolId) { const type = getInstrumentType(symbolId); if (type === 'spot' && symbolId.startsWith('S')) { return symbolId.charAt(0).toLowerCase() + symbolId.slice(1); } if (symbolId.startsWith('U100')) { return symbolId.charAt(0).toLowerCase() + symbolId.slice(1); } if (symbolId === 'CETHUSD') { return symbolId.charAt(0).toLowerCase() + symbolId.slice(1); } return symbolId; } function getSymbols(symbols) { const perpV2Symbols = symbols.filter((s) => getInstrumentType(s) === 'perpetual' && s.endsWith('USDT')).map(getApiSymbolId); const otherSymbols = symbols.filter((s) => getInstrumentType(s) !== 'perpetual' || s.endsWith('USDT') == false).map(getApiSymbolId); return { perpV2Symbols, otherSymbols }; } exports.phemexTradesMapper = { canHandle(message) { return message.type === 'incremental' && ('trades' in message || 'trades_p' in message); }, getFilters(symbols) { if (symbols == undefined || symbols.length === 0) { return [ { channel: 'trades' }, { channel: 'trades_p' } ]; } const { perpV2Symbols, otherSymbols } = getSymbols(symbols); const filters = []; if (perpV2Symbols.length > 0) { filters.push({ channel: 'trades_p', symbols: perpV2Symbols }); } if (otherSymbols.length > 0) { filters.push({ channel: 'trades', symbols: otherSymbols }); } return filters; }, *map(message, localTimestamp) { if ('trades' in message) { for (const [timestamp, side, priceEp, qty] of message.trades) { const symbol = message.symbol; yield { type: 'trade', symbol: symbol.toUpperCase(), exchange: 'phemex', id: undefined, price: priceEp / getPriceScale(symbol), amount: qty / getQtyScale(symbol), side: side === 'Buy' ? 'buy' : 'sell', timestamp: fromNanoSecondsToDate(timestamp), localTimestamp: localTimestamp }; } } else if ('trades_p' in message) { for (const [timestamp, side, price, qty] of message.trades_p) { const symbol = message.symbol; yield { type: 'trade', symbol: symbol.toUpperCase(), exchange: 'phemex', id: undefined, price: Number(price), amount: Number(qty), side: side === 'Buy' ? 'buy' : 'sell', timestamp: fromNanoSecondsToDate(timestamp), localTimestamp: localTimestamp }; } } } }; const mapBookLevelForSymbol = (symbol) => ([priceEp, qty]) => { return { price: priceEp / getPriceScale(symbol), amount: qty / getQtyScale(symbol) }; }; function mapPerpBookLevel([price, amount]) { return { price: Number(price), amount: Number(amount) }; } exports.phemexBookChangeMapper = { canHandle(message) { return 'book' in message || 'orderbook_p' in message; }, getFilters(symbols) { if (symbols == undefined || symbols.length === 0) { return [ { channel: 'book' }, { channel: 'orderbook_p' } ]; } const { perpV2Symbols, otherSymbols } = getSymbols(symbols); const filters = []; if (perpV2Symbols.length > 0) { filters.push({ channel: 'orderbook_p', symbols: perpV2Symbols }); } if (otherSymbols.length > 0) { filters.push({ channel: 'book', symbols: otherSymbols }); } return filters; }, *map(message, localTimestamp) { const symbol = message.symbol; if ('book' in message) { const mapBookLevel = mapBookLevelForSymbol(symbol); yield { type: 'book_change', symbol: symbol.toUpperCase(), exchange: 'phemex', isSnapshot: message.type === 'snapshot', bids: message.book.bids.map(mapBookLevel), asks: message.book.asks.map(mapBookLevel), timestamp: fromNanoSecondsToDate(message.timestamp), localTimestamp }; } else if ('orderbook_p' in message) { yield { type: 'book_change', symbol: symbol.toUpperCase(), exchange: 'phemex', isSnapshot: message.type === 'snapshot', bids: message.orderbook_p.bids.map(mapPerpBookLevel), asks: message.orderbook_p.asks.map(mapPerpBookLevel), timestamp: fromNanoSecondsToDate(message.timestamp), localTimestamp }; } } }; class PhemexDerivativeTickerMapper { constructor() { this.pendingTickerInfoHelper = new mapper_1.PendingTickerInfoHelper(); } canHandle(message) { return 'market24h' in message || message.method === 'perp_market24h_pack_p.update'; } getFilters(symbols) { if (symbols == undefined || symbols.length === 0) { return [ { channel: 'market24h' }, { channel: 'perp_market24h_pack_p' } ]; } const { perpV2Symbols, otherSymbols } = getSymbols(symbols); const filters = []; if (perpV2Symbols.length > 0) { filters.push({ channel: 'perp_market24h_pack_p', symbols: perpV2Symbols }); } if (otherSymbols.length > 0) { filters.push({ channel: 'market24h', symbols: otherSymbols }); } return filters; } *map(message, localTimestamp) { if ('market24h' in message) { const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(message.market24h.symbol, 'phemex'); const phemexTicker = message.market24h; pendingTickerInfo.updateFundingRate(phemexTicker.fundingRate / 100000000); pendingTickerInfo.updatePredictedFundingRate(phemexTicker.predFundingRate / 100000000); pendingTickerInfo.updateIndexPrice(phemexTicker.indexPrice / 10000); pendingTickerInfo.updateMarkPrice(phemexTicker.markPrice / 10000); pendingTickerInfo.updateOpenInterest(phemexTicker.openInterest); pendingTickerInfo.updateLastPrice(phemexTicker.close / 10000); pendingTickerInfo.updateTimestamp(fromNanoSecondsToDate(message.timestamp)); if (pendingTickerInfo.hasChanged()) { yield pendingTickerInfo.getSnapshot(localTimestamp); } } else { for (let [symbol, _openRp, _highRp, _lowRp, lastRp, _volumeRq, _turnoverRv, openInterestRv, indexRp, markRp, fundingRateRr, predFundingRateRr] of message.data) { const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(symbol, 'phemex'); pendingTickerInfo.updateFundingRate(Number(fundingRateRr)); pendingTickerInfo.updatePredictedFundingRate(Number(predFundingRateRr)); pendingTickerInfo.updateIndexPrice(Number(indexRp)); pendingTickerInfo.updateMarkPrice(Number(markRp)); pendingTickerInfo.updateOpenInterest(Number(openInterestRv)); pendingTickerInfo.updateLastPrice(Number(lastRp)); pendingTickerInfo.updateTimestamp(fromNanoSecondsToDate(message.timestamp)); if (pendingTickerInfo.hasChanged()) { yield pendingTickerInfo.getSnapshot(localTimestamp); } } } } } exports.PhemexDerivativeTickerMapper = PhemexDerivativeTickerMapper; //# sourceMappingURL=phemex.js.map