tardis-dev
Version:
Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js
526 lines • 21.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BybitLiquidationsMapper = exports.BybitDerivativeTickerMapper = exports.BybitBookChangeMapper = exports.BybitTradesMapper = exports.BybitV5OptionSummaryMapper = exports.BybitV5LiquidationsMapper = exports.BybitV5DerivativeTickerMapper = exports.BybitV5BookTickerMapper = exports.BybitV5BookChangeMapper = exports.BybitV5TradesMapper = void 0;
const handy_1 = require("../handy");
const mapper_1 = require("./mapper");
// v5 https://bybit-exchange.github.io/docs/v5/ws/connect
class BybitV5TradesMapper {
constructor(_exchange) {
this._exchange = _exchange;
}
canHandle(message) {
if (message.topic === undefined) {
return false;
}
return message.topic.startsWith('publicTrade.');
}
getFilters(symbols) {
symbols = (0, handy_1.upperCaseSymbols)(symbols);
return [
{
channel: 'publicTrade',
symbols
}
];
}
*map(message, localTimestamp) {
for (const trade of message.data) {
yield {
type: 'trade',
symbol: trade.s,
exchange: this._exchange,
id: trade.i,
price: Number(trade.p),
amount: Number(trade.v),
side: trade.S == 'Buy' ? 'buy' : trade.S === 'Sell' ? 'sell' : 'unknown',
timestamp: new Date(trade.T),
localTimestamp
};
}
}
}
exports.BybitV5TradesMapper = BybitV5TradesMapper;
class BybitV5BookChangeMapper {
constructor(_exchange, _depth) {
this._exchange = _exchange;
this._depth = _depth;
}
canHandle(message) {
if (message.topic === undefined) {
return false;
}
return message.topic.startsWith(`orderbook.${this._depth}.`);
}
getFilters(symbols) {
symbols = (0, handy_1.upperCaseSymbols)(symbols);
return [
{
channel: `orderbook.${this._depth}`,
symbols
}
];
}
*map(message, localTimestamp) {
yield {
type: 'book_change',
symbol: message.data.s,
exchange: this._exchange,
isSnapshot: message.type === 'snapshot',
bids: message.data.b.map(this._mapBookLevel),
asks: message.data.a.map(this._mapBookLevel),
timestamp: new Date(message.ts),
localTimestamp
};
}
_mapBookLevel(level) {
return { price: Number(level[0]), amount: Number(level[1]) };
}
}
exports.BybitV5BookChangeMapper = BybitV5BookChangeMapper;
class BybitV5BookTickerMapper {
constructor(_exchange) {
this._exchange = _exchange;
this._snapshots = {};
}
canHandle(message) {
if (message.topic === undefined) {
return false;
}
return message.topic.startsWith(`orderbook.1.`);
}
getFilters(symbols) {
symbols = (0, handy_1.upperCaseSymbols)(symbols);
return [
{
channel: 'orderbook.1',
symbols
}
];
}
*map(message, localTimestamp) {
const bestAsk = message.data.a.filter((ask) => ask[1] != '0')[0];
const bestBid = message.data.b.filter((bid) => bid[1] != '0')[0];
if (message.type === 'snapshot') {
this._snapshots[message.data.s] = {
askAmount: bestAsk !== undefined ? Number(bestAsk[1]) : undefined,
askPrice: bestAsk !== undefined ? Number(bestAsk[0]) : undefined,
bidPrice: bestBid !== undefined ? Number(bestBid[0]) : undefined,
bidAmount: bestBid !== undefined ? Number(bestBid[1]) : undefined
};
}
const matchingSnapshot = this._snapshots[message.data.s];
if (!matchingSnapshot) {
return;
}
const bookTicker = {
type: 'book_ticker',
symbol: message.data.s,
exchange: this._exchange,
askAmount: bestAsk !== undefined ? Number(bestAsk[1]) : matchingSnapshot.askAmount,
askPrice: bestAsk !== undefined ? Number(bestAsk[0]) : matchingSnapshot.askPrice,
bidPrice: bestBid !== undefined ? Number(bestBid[0]) : matchingSnapshot.bidPrice,
bidAmount: bestBid !== undefined ? Number(bestBid[1]) : matchingSnapshot.bidAmount,
timestamp: new Date(message.ts),
localTimestamp: localTimestamp
};
this._snapshots[message.data.s] = {
askAmount: bookTicker.askAmount,
askPrice: bookTicker.askPrice,
bidPrice: bookTicker.bidPrice,
bidAmount: bookTicker.bidAmount
};
yield bookTicker;
}
}
exports.BybitV5BookTickerMapper = BybitV5BookTickerMapper;
class BybitV5DerivativeTickerMapper {
constructor() {
this.pendingTickerInfoHelper = new mapper_1.PendingTickerInfoHelper();
}
canHandle(message) {
if (message.topic === undefined) {
return false;
}
return message.topic.startsWith('tickers.');
}
getFilters(symbols) {
symbols = (0, handy_1.upperCaseSymbols)(symbols);
return [
{
channel: 'tickers',
symbols
}
];
}
*map(message, localTimestamp) {
const instrumentInfo = message.data;
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(instrumentInfo.symbol, 'bybit');
if (instrumentInfo.fundingRate !== undefined && instrumentInfo.fundingRate !== '') {
pendingTickerInfo.updateFundingRate(Number(instrumentInfo.fundingRate));
}
if (instrumentInfo.nextFundingTime !== undefined && instrumentInfo.nextFundingTime !== '') {
pendingTickerInfo.updateFundingTimestamp(new Date(Number(instrumentInfo.nextFundingTime)));
}
if (instrumentInfo.indexPrice !== undefined && instrumentInfo.indexPrice !== '') {
pendingTickerInfo.updateIndexPrice(Number(instrumentInfo.indexPrice));
}
if (instrumentInfo.markPrice !== undefined && instrumentInfo.markPrice !== '') {
pendingTickerInfo.updateMarkPrice(Number(instrumentInfo.markPrice));
}
if (instrumentInfo.openInterest !== undefined && instrumentInfo.openInterest !== '') {
pendingTickerInfo.updateOpenInterest(Number(instrumentInfo.openInterest));
}
if (instrumentInfo.lastPrice !== undefined && instrumentInfo.lastPrice !== '') {
pendingTickerInfo.updateLastPrice(Number(instrumentInfo.lastPrice));
}
pendingTickerInfo.updateTimestamp(new Date(message.ts));
if (pendingTickerInfo.hasChanged()) {
yield pendingTickerInfo.getSnapshot(localTimestamp);
}
}
}
exports.BybitV5DerivativeTickerMapper = BybitV5DerivativeTickerMapper;
class BybitV5LiquidationsMapper {
constructor(_exchange) {
this._exchange = _exchange;
}
canHandle(message) {
if (message.topic === undefined) {
return false;
}
return message.topic.startsWith('liquidation.');
}
getFilters(symbols) {
symbols = (0, handy_1.upperCaseSymbols)(symbols);
return [
{
channel: 'liquidation',
symbols
}
];
}
*map(message, localTimestamp) {
// from bybit telegram: When "side":"Buy", a long position was liquidated. Will fix the docs.
const bybitLiquidation = message.data;
const liquidation = {
type: 'liquidation',
symbol: bybitLiquidation.symbol,
exchange: this._exchange,
id: undefined,
price: Number(bybitLiquidation.price),
amount: Number(bybitLiquidation.size),
side: bybitLiquidation.side == 'Buy' ? 'sell' : 'buy',
timestamp: new Date(message.ts),
localTimestamp
};
yield liquidation;
}
}
exports.BybitV5LiquidationsMapper = BybitV5LiquidationsMapper;
class BybitV5OptionSummaryMapper {
canHandle(message) {
if (message.topic === undefined) {
return false;
}
return message.topic.startsWith('tickers.');
}
getFilters(symbols) {
symbols = (0, handy_1.upperCaseSymbols)(symbols);
return [
{
channel: 'tickers',
symbols
}
];
}
*map(message, localTimestamp) {
const symbolParts = message.data.symbol.split('-');
const isPut = symbolParts[3] === 'P';
const strikePrice = Number(symbolParts[2]);
const expirationDate = new Date(symbolParts[1] + 'Z');
expirationDate.setUTCHours(8);
const optionSummary = {
type: 'option_summary',
symbol: message.data.symbol,
exchange: 'bybit-options',
optionType: isPut ? 'put' : 'call',
strikePrice,
expirationDate,
bestBidPrice: (0, handy_1.asNumberIfValid)(message.data.bidPrice),
bestBidAmount: (0, handy_1.asNumberIfValid)(message.data.bidSize),
bestBidIV: (0, handy_1.asNumberIfValid)(message.data.bidIv),
bestAskPrice: (0, handy_1.asNumberIfValid)(message.data.askPrice),
bestAskAmount: (0, handy_1.asNumberIfValid)(message.data.askSize),
bestAskIV: (0, handy_1.asNumberIfValid)(message.data.askIv),
lastPrice: (0, handy_1.asNumberIfValid)(message.data.lastPrice),
openInterest: (0, handy_1.asNumberIfValid)(message.data.openInterest),
markPrice: (0, handy_1.asNumberIfValid)(message.data.markPrice),
markIV: (0, handy_1.asNumberIfValid)(message.data.markPriceIv),
delta: (0, handy_1.asNumberIfValid)(message.data.delta),
gamma: (0, handy_1.asNumberIfValid)(message.data.gamma),
vega: (0, handy_1.asNumberIfValid)(message.data.vega),
theta: (0, handy_1.asNumberIfValid)(message.data.theta),
rho: undefined,
underlyingPrice: (0, handy_1.asNumberIfValid)(message.data.underlyingPrice),
underlyingIndex: '',
timestamp: new Date(message.ts),
localTimestamp: localTimestamp
};
yield optionSummary;
}
}
exports.BybitV5OptionSummaryMapper = BybitV5OptionSummaryMapper;
// https://github.com/bybit-exchange/bybit-official-api-docs/blob/master/en/websocket.md
class BybitTradesMapper {
constructor(_exchange) {
this._exchange = _exchange;
}
canHandle(message) {
if (message.topic === undefined) {
return false;
}
return message.topic.startsWith('trade.');
}
getFilters(symbols) {
symbols = (0, handy_1.upperCaseSymbols)(symbols);
return [
{
channel: 'trade',
symbols
}
];
}
*map(message, localTimestamp) {
for (const trade of message.data) {
const timestamp = 'trade_time_ms' in trade
? new Date(Number(trade.trade_time_ms))
: 'tradeTimeMs' in trade
? new Date(Number(trade.tradeTimeMs))
: new Date(trade.timestamp);
yield {
type: 'trade',
symbol: trade.symbol,
exchange: this._exchange,
id: 'trade_id' in trade ? trade.trade_id : trade.tradeId,
price: Number(trade.price),
amount: trade.size,
side: trade.side == 'Buy' ? 'buy' : trade.side === 'Sell' ? 'sell' : 'unknown',
timestamp,
localTimestamp
};
}
}
}
exports.BybitTradesMapper = BybitTradesMapper;
class BybitBookChangeMapper {
constructor(_exchange, _canUseBook200Channel) {
this._exchange = _exchange;
this._canUseBook200Channel = _canUseBook200Channel;
}
canHandle(message) {
if (message.topic === undefined) {
return false;
}
if (this._canUseBook200Channel) {
return message.topic.startsWith('orderBook_200.');
}
else {
return message.topic.startsWith('orderBookL2_25.');
}
}
getFilters(symbols) {
symbols = (0, handy_1.upperCaseSymbols)(symbols);
if (this._canUseBook200Channel) {
return [
{
channel: 'orderBook_200',
symbols
}
];
}
return [
{
channel: 'orderBookL2_25',
symbols
}
];
}
*map(message, localTimestamp) {
const topicArray = message.topic.split('.');
const symbol = topicArray[topicArray.length - 1];
const data = message.type === 'snapshot'
? 'order_book' in message.data
? message.data.order_book
: 'orderBook' in message.data
? message.data.orderBook
: message.data
: [...message.data.delete, ...message.data.update, ...message.data.insert];
const timestampBybit = message.timestamp_e6 !== undefined ? Number(message.timestamp_e6) : Number(message.timestampE6);
const timestamp = new Date(timestampBybit / 1000);
timestamp.μs = timestampBybit % 1000;
yield {
type: 'book_change',
symbol,
exchange: this._exchange,
isSnapshot: message.type === 'snapshot',
bids: data.filter((d) => d.side === 'Buy').map(this._mapBookLevel),
asks: data.filter((d) => d.side === 'Sell').map(this._mapBookLevel),
timestamp,
localTimestamp
};
}
_mapBookLevel(level) {
return { price: Number(level.price), amount: level.size !== undefined ? level.size : 0 };
}
}
exports.BybitBookChangeMapper = BybitBookChangeMapper;
class BybitDerivativeTickerMapper {
constructor() {
this.pendingTickerInfoHelper = new mapper_1.PendingTickerInfoHelper();
}
canHandle(message) {
if (message.topic === undefined) {
return false;
}
return message.topic.startsWith('instrument_info.');
}
getFilters(symbols) {
symbols = (0, handy_1.upperCaseSymbols)(symbols);
return [
{
channel: 'instrument_info',
symbols
}
];
}
*map(message, localTimestamp) {
const instrumentInfo = 'symbol' in message.data ? message.data : message.data.update[0];
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(instrumentInfo.symbol, 'bybit');
const fundingRate = 'funding_rate_e6' in instrumentInfo ? instrumentInfo.funding_rate_e6 : instrumentInfo.fundingRateE6;
pendingTickerInfo.updateFundingRate(fundingRate !== undefined ? Number(fundingRate) / 1000000 : undefined);
const predictedFundingRate = 'predicted_funding_rate_e6' in instrumentInfo ? instrumentInfo.predicted_funding_rate_e6 : instrumentInfo.predictedFundingRateE6;
pendingTickerInfo.updatePredictedFundingRate(predictedFundingRate !== undefined ? Number(predictedFundingRate) / 1000000 : undefined);
const nextFundingTime = 'next_funding_time' in instrumentInfo ? instrumentInfo.next_funding_time : instrumentInfo.nextFundingTime;
pendingTickerInfo.updateFundingTimestamp(nextFundingTime !== undefined && new Date(nextFundingTime).valueOf() > 0 ? new Date(nextFundingTime) : undefined);
if (instrumentInfo.index_price !== undefined) {
pendingTickerInfo.updateIndexPrice(Number(instrumentInfo.index_price));
}
else if (instrumentInfo.indexPrice !== undefined) {
pendingTickerInfo.updateIndexPrice(Number(instrumentInfo.indexPrice));
}
else if (instrumentInfo.index_price_e4 !== undefined) {
pendingTickerInfo.updateIndexPrice(Number(instrumentInfo.index_price_e4) / 10000);
}
else if (instrumentInfo.indexPriceE4 !== undefined) {
pendingTickerInfo.updateIndexPrice(Number(instrumentInfo.indexPriceE4) / 10000);
}
if (instrumentInfo.mark_price !== undefined) {
pendingTickerInfo.updateMarkPrice(Number(instrumentInfo.mark_price));
}
else if (instrumentInfo.markPrice !== undefined) {
pendingTickerInfo.updateMarkPrice(Number(instrumentInfo.markPrice));
}
else if (instrumentInfo.mark_price_e4 !== undefined) {
pendingTickerInfo.updateMarkPrice(Number(instrumentInfo.mark_price_e4) / 10000);
}
else if (instrumentInfo.markPriceE4 !== undefined) {
pendingTickerInfo.updateMarkPrice(Number(instrumentInfo.markPriceE4) / 10000);
}
if (instrumentInfo.open_interest !== undefined) {
pendingTickerInfo.updateOpenInterest(instrumentInfo.open_interest);
}
else if (instrumentInfo.openInterestE8 !== undefined) {
pendingTickerInfo.updateOpenInterest(Number(instrumentInfo.openInterestE8) / 100000000);
}
else if (instrumentInfo.open_interest_e8 !== undefined) {
pendingTickerInfo.updateOpenInterest(instrumentInfo.open_interest_e8 / 100000000);
}
if (instrumentInfo.last_price !== undefined) {
pendingTickerInfo.updateLastPrice(Number(instrumentInfo.last_price));
}
else if (instrumentInfo.lastPrice !== undefined) {
pendingTickerInfo.updateLastPrice(Number(instrumentInfo.lastPrice));
}
else if (instrumentInfo.last_price_e4 !== undefined) {
pendingTickerInfo.updateLastPrice(Number(instrumentInfo.last_price_e4) / 10000);
}
else if (instrumentInfo.lastPriceE4) {
pendingTickerInfo.updateLastPrice(Number(instrumentInfo.lastPriceE4) / 10000);
}
if (message.timestamp_e6 !== undefined) {
const timestampBybit = Number(message.timestamp_e6);
const timestamp = new Date(timestampBybit / 1000);
timestamp.μs = timestampBybit % 1000;
pendingTickerInfo.updateTimestamp(timestamp);
}
else if (message.timestampE6 !== undefined) {
const timestampBybit = Number(message.timestampE6);
const timestamp = new Date(timestampBybit / 1000);
timestamp.μs = timestampBybit % 1000;
pendingTickerInfo.updateTimestamp(timestamp);
}
else {
pendingTickerInfo.updateTimestamp(new Date(instrumentInfo.updated_at));
}
if (pendingTickerInfo.hasChanged()) {
yield pendingTickerInfo.getSnapshot(localTimestamp);
}
}
}
exports.BybitDerivativeTickerMapper = BybitDerivativeTickerMapper;
class BybitLiquidationsMapper {
constructor(_exchange) {
this._exchange = _exchange;
}
canHandle(message) {
if (message.topic === undefined) {
return false;
}
return message.topic.startsWith('liquidation.');
}
getFilters(symbols) {
symbols = (0, handy_1.upperCaseSymbols)(symbols);
return [
{
channel: 'liquidation',
symbols
}
];
}
*map(message, localTimestamp) {
// from bybit telegram: When "side":"Buy", a long position was liquidated. Will fix the docs.
if (message.generated) {
for (const bybitLiquidation of message.data) {
const liquidation = {
type: 'liquidation',
symbol: bybitLiquidation.symbol,
exchange: this._exchange,
id: String(bybitLiquidation.id),
price: Number(bybitLiquidation.price),
amount: bybitLiquidation.qty,
side: bybitLiquidation.side == 'Buy' ? 'sell' : 'buy',
timestamp: new Date(bybitLiquidation.time),
localTimestamp
};
yield liquidation;
}
}
else {
const bybitLiquidation = message.data;
const liquidation = {
type: 'liquidation',
symbol: bybitLiquidation.symbol,
exchange: this._exchange,
id: undefined,
price: Number(bybitLiquidation.price),
amount: Number(bybitLiquidation.qty),
side: bybitLiquidation.side == 'Buy' ? 'sell' : 'buy',
timestamp: new Date(bybitLiquidation.time),
localTimestamp
};
yield liquidation;
}
}
}
exports.BybitLiquidationsMapper = BybitLiquidationsMapper;
//# sourceMappingURL=bybit.js.map