tardis-dev
Version:
Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js
260 lines • 9.62 kB
JavaScript
;
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