tardis-dev
Version:
Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js
795 lines • 31 kB
JavaScript
import { asNonZeroNumberOrUndefined, upperCaseSymbols } from "../handy.js";
import { PendingTickerInfoHelper } from "./mapper.js";
// V5 Okex API mappers
// https://www.okex.com/docs-v5/en/#websocket-api-public-channel-trades-channel
export class OkexV5TradesMapper {
_exchange;
_useTradesAll;
constructor(_exchange, _useTradesAll) {
this._exchange = _exchange;
this._useTradesAll = _useTradesAll;
}
canHandle(message) {
if (message.event !== undefined || message.arg === undefined) {
return false;
}
return message.arg.channel === 'trades' || message.arg.channel === 'trades-all';
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
if (this._useTradesAll) {
return [
{
channel: `trades-all`,
symbols
}
];
}
return [
{
channel: `trades`,
symbols
}
];
}
*map(okexTradesMessage, localTimestamp) {
for (const okexTrade of okexTradesMessage.data) {
yield {
type: 'trade',
symbol: okexTrade.instId,
exchange: this._exchange,
id: okexTrade.tradeId,
price: Number(okexTrade.px),
amount: Number(okexTrade.sz),
side: okexTrade.side === 'buy' ? 'buy' : 'sell',
timestamp: new Date(Number(okexTrade.ts)),
localTimestamp: localTimestamp
};
}
}
}
const mapV5BookLevel = (level) => {
const price = Number(level[0]);
const amount = Number(level[1]);
return { price, amount };
};
export class OkexV5BookChangeMapper {
_exchange;
_channelName;
constructor(_exchange, usePublicBooksChannel) {
this._exchange = _exchange;
this._channelName = this._getBooksChannelName(usePublicBooksChannel);
}
canHandle(message) {
if (message.event !== undefined || message.arg === undefined) {
return false;
}
return message.arg.channel === this._channelName;
}
_hasCredentials = process.env.OKX_API_KEY !== undefined;
_hasVip5Access = process.env.OKX_API_VIP_5 !== undefined;
_hasColoAccess = process.env.OKX_API_COLO !== undefined;
_getBooksChannelName(usePublicBooksChannel) {
if (usePublicBooksChannel === false) {
// historical data always uses books-l2-tbt
return 'books-l2-tbt';
}
if (this._hasCredentials && this._hasVip5Access) {
return 'books-l2-tbt';
}
if (this._hasColoAccess) {
return 'books-l2-tbt';
}
if (this._hasCredentials) {
return 'books50-l2-tbt';
}
return 'books';
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
return [
{
channel: this._channelName,
symbols
}
];
}
*map(okexDepthDataMessage, localTimestamp) {
for (const message of okexDepthDataMessage.data) {
if (okexDepthDataMessage.action === 'update' && message.bids.length === 0 && message.asks.length === 0) {
continue;
}
const timestamp = new Date(Number(message.ts));
if (timestamp.valueOf() === 0) {
continue;
}
yield {
type: 'book_change',
symbol: okexDepthDataMessage.arg.instId,
exchange: this._exchange,
isSnapshot: okexDepthDataMessage.action === 'snapshot',
bids: message.bids.map(mapV5BookLevel),
asks: message.asks.map(mapV5BookLevel),
timestamp,
localTimestamp: localTimestamp
};
}
}
}
export class OkexV5BookTickerMapper {
_exchange;
_useTbtTickerChannel;
constructor(_exchange, _useTbtTickerChannel) {
this._exchange = _exchange;
this._useTbtTickerChannel = _useTbtTickerChannel;
}
canHandle(message) {
if (message.event !== undefined || message.arg === undefined) {
return false;
}
if (this._useTbtTickerChannel) {
return message.arg.channel === 'bbo-tbt';
}
return message.arg.channel === 'tickers';
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
if (this._useTbtTickerChannel) {
return [
{
channel: `bbo-tbt`,
symbols
}
];
}
return [
{
channel: `tickers`,
symbols
}
];
}
map(message, localTimestamp) {
if (message.arg.channel === 'bbo-tbt') {
return this._mapFromTbtTicker(message, localTimestamp);
}
else {
return this._mapFromTicker(message, localTimestamp);
}
}
*_mapFromTbtTicker(message, localTimestamp) {
if (!message.data) {
return;
}
for (const tbtTicker of message.data) {
const bestAsk = tbtTicker.asks !== undefined && tbtTicker.asks[0] ? mapBookLevel(tbtTicker.asks[0]) : undefined;
const bestBid = tbtTicker.bids !== undefined && tbtTicker.bids[0] ? mapBookLevel(tbtTicker.bids[0]) : undefined;
const ticker = {
type: 'book_ticker',
symbol: message.arg.instId,
exchange: this._exchange,
askAmount: bestAsk?.amount,
askPrice: bestAsk?.price,
bidPrice: bestBid?.price,
bidAmount: bestBid?.amount,
timestamp: new Date(Number(tbtTicker.ts)),
localTimestamp: localTimestamp
};
yield ticker;
}
}
*_mapFromTicker(message, localTimestamp) {
for (const okexTicker of message.data) {
const ticker = {
type: 'book_ticker',
symbol: okexTicker.instId,
exchange: this._exchange,
askAmount: asNonZeroNumberOrUndefined(okexTicker.askSz),
askPrice: asNonZeroNumberOrUndefined(okexTicker.askPx),
bidPrice: asNonZeroNumberOrUndefined(okexTicker.bidPx),
bidAmount: asNonZeroNumberOrUndefined(okexTicker.bidSz),
timestamp: new Date(Number(okexTicker.ts)),
localTimestamp: localTimestamp
};
yield ticker;
}
}
}
export class OkexV5DerivativeTickerMapper {
_exchange;
pendingTickerInfoHelper = new PendingTickerInfoHelper();
_indexPrices = new Map();
_futuresChannels = ['tickers', 'open-interest', 'mark-price', 'index-tickers'];
_swapChannels = ['tickers', 'open-interest', 'mark-price', 'index-tickers', 'funding-rate'];
constructor(_exchange) {
this._exchange = _exchange;
}
canHandle(message) {
const channels = this._exchange === 'okex-futures' ? this._futuresChannels : this._swapChannels;
if (message.event !== undefined || message.arg === undefined) {
return false;
}
return channels.includes(message.arg.channel);
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
const channels = this._exchange === 'okex-futures' ? this._futuresChannels : this._swapChannels;
return channels.map((channel) => {
if (channel === 'index-tickers') {
const indexes = symbols !== undefined
? symbols.map((s) => {
const symbolParts = s.split('-');
const quotePart = symbolParts[1] === 'USDC' ? 'USD' : symbolParts[1];
return `${symbolParts[0]}-${quotePart}`;
})
: undefined;
return {
channel,
symbols: indexes
};
}
return {
channel,
symbols
};
});
}
*map(message, localTimestamp) {
if (message.arg.channel === 'index-tickers') {
for (const dataMessage of message.data) {
const indexTickerMessage = dataMessage;
const lastIndexPrice = Number(indexTickerMessage.idxPx);
if (lastIndexPrice > 0) {
this._indexPrices.set(indexTickerMessage.instId, lastIndexPrice);
}
}
return;
}
for (const dataMessage of message.data) {
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(dataMessage.instId, this._exchange);
const symbolParts = dataMessage.instId.split('-');
const quotePart = symbolParts[1] === 'USDC' ? 'USD' : symbolParts[1];
const indexSymbol = `${symbolParts[0]}-${quotePart}`;
const indexPrice = this._indexPrices.get(indexSymbol);
if (indexPrice !== undefined) {
pendingTickerInfo.updateIndexPrice(indexPrice);
}
if (message.arg.channel === 'mark-price') {
const markPriceMessage = dataMessage;
const markPrice = Number(markPriceMessage.markPx);
if (markPrice > 0) {
pendingTickerInfo.updateMarkPrice(markPrice);
pendingTickerInfo.updateTimestamp(new Date(Number(markPriceMessage.ts)));
}
}
if (message.arg.channel === 'open-interest') {
const openInterestMessage = dataMessage;
const openInterest = Number(openInterestMessage.oi);
if (openInterest > 0) {
pendingTickerInfo.updateOpenInterest(openInterest);
pendingTickerInfo.updateTimestamp(new Date(Number(openInterestMessage.ts)));
}
}
if (message.arg.channel === 'funding-rate') {
const fundingRateMessage = dataMessage;
if (fundingRateMessage.fundingRate !== undefined) {
pendingTickerInfo.updateFundingRate(Number(fundingRateMessage.fundingRate));
}
if (fundingRateMessage.fundingTime !== undefined) {
pendingTickerInfo.updateFundingTimestamp(new Date(Number(fundingRateMessage.fundingTime)));
}
if (fundingRateMessage.nextFundingRate !== undefined && fundingRateMessage.nextFundingRate !== '') {
pendingTickerInfo.updatePredictedFundingRate(Number(fundingRateMessage.nextFundingRate));
}
}
if (message.arg.channel === 'tickers') {
const tickerMessage = dataMessage;
const lastPrice = Number(tickerMessage.last);
if (lastPrice > 0) {
pendingTickerInfo.updateLastPrice(lastPrice);
pendingTickerInfo.updateTimestamp(new Date(Number(tickerMessage.ts)));
}
}
if (pendingTickerInfo.hasChanged()) {
yield pendingTickerInfo.getSnapshot(localTimestamp);
}
}
}
}
export class OkexV5LiquidationsMapper {
_exchange;
_isFirstMessage = true;
constructor(_exchange) {
this._exchange = _exchange;
}
canHandle(message) {
if (message.event !== undefined || message.arg === undefined) {
return false;
}
return message.arg.channel === 'liquidations' || message.arg.channel === 'liquidation-orders';
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
return [
{
channel: 'liquidations',
symbols
},
{
channel: 'liquidation-orders',
symbols
}
];
}
*map(okexLiquidationMessage, localTimestamp) {
if (okexLiquidationMessage.arg.channel === 'liquidation-orders') {
if (this._isFirstMessage) {
this._isFirstMessage = false;
return;
}
for (const okexLiquidation of okexLiquidationMessage.data) {
for (const detail of okexLiquidation.details) {
const liquidation = {
type: 'liquidation',
symbol: okexLiquidation.instId,
exchange: this._exchange,
id: undefined,
price: Number(detail.bkPx),
amount: Number(detail.sz),
side: detail.side === 'buy' ? 'buy' : 'sell',
timestamp: new Date(Number(detail.ts)),
localTimestamp: localTimestamp
};
yield liquidation;
}
}
}
else {
for (const okexLiquidation of okexLiquidationMessage.data) {
const liquidation = {
type: 'liquidation',
symbol: okexLiquidationMessage.arg.instId,
exchange: this._exchange,
id: undefined,
price: Number(okexLiquidation.bkPx),
amount: Number(okexLiquidation.sz),
side: okexLiquidation.side === 'buy' ? 'buy' : 'sell',
timestamp: new Date(Number(okexLiquidation.ts)),
localTimestamp: localTimestamp
};
yield liquidation;
}
}
}
}
export class OkexV5OptionSummaryMapper {
_indexPrices = new Map();
_openInterests = new Map();
_markPrices = new Map();
_tickers = new Map();
expiration_regex = /(\d{2})(\d{2})(\d{2})/;
canHandle(message) {
if (message.event !== undefined || message.arg === undefined) {
return false;
}
return (message.arg.channel === 'opt-summary' ||
message.arg.channel === 'index-tickers' ||
message.arg.channel === 'tickers' ||
message.arg.channel === 'open-interest' ||
message.arg.channel === 'mark-price');
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
const indexes = symbols !== undefined
? symbols.map((s) => {
const symbolParts = s.split('-');
return `${symbolParts[0]}-${symbolParts[1]}`;
})
: undefined;
return [
{
channel: `opt-summary`,
symbols: []
},
{
channel: `index-tickers`,
symbols: indexes
},
{
channel: `tickers`,
symbols: symbols
},
{
channel: `open-interest`,
symbols: symbols
},
{
channel: `mark-price`,
symbols: symbols
}
];
}
*map(message, localTimestamp) {
if (message.arg.channel === 'index-tickers') {
for (const dataMessage of message.data) {
const indexTickerMessage = dataMessage;
const lastIndexPrice = asNonZeroNumberOrUndefined(indexTickerMessage.idxPx);
if (lastIndexPrice !== undefined) {
this._indexPrices.set(indexTickerMessage.instId, lastIndexPrice);
}
}
return;
}
if (message.arg.channel === 'open-interest') {
for (const dataMessage of message.data) {
const openInterestMessage = dataMessage;
const openInterestValue = asNonZeroNumberOrUndefined(openInterestMessage.oi);
if (openInterestValue !== undefined) {
this._openInterests.set(openInterestMessage.instId, openInterestValue);
}
}
return;
}
if (message.arg.channel === 'mark-price') {
for (const dataMessage of message.data) {
const markPriceMessage = dataMessage;
const markPrice = asNonZeroNumberOrUndefined(markPriceMessage.markPx);
if (markPrice !== undefined) {
this._markPrices.set(markPriceMessage.instId, markPrice);
}
}
return;
}
if (message.arg.channel === 'tickers') {
for (const dataMessage of message.data) {
const tickerMessage = dataMessage;
this._tickers.set(tickerMessage.instId, tickerMessage);
}
return;
}
if (message.arg.channel === 'opt-summary') {
for (const dataMessage of message.data) {
const summary = dataMessage;
const symbolParts = summary.instId.split('-');
const isPut = symbolParts[4] === 'P';
const strikePrice = Number(symbolParts[3]);
var dateArray = this.expiration_regex.exec(symbolParts[2]);
const expirationDate = new Date(Date.UTC(+('20' + dateArray[1]), +dateArray[2] - 1, +dateArray[3], 8, 0, 0, 0));
const lastUnderlyingPrice = this._indexPrices.get(summary.uly);
const lastOpenInterest = this._openInterests.get(summary.instId);
const lastMarkPrice = this._markPrices.get(summary.instId);
const lastTickerInfo = this._tickers.get(summary.instId);
const optionSummary = {
type: 'option_summary',
symbol: summary.instId,
exchange: 'okex-options',
optionType: isPut ? 'put' : 'call',
strikePrice,
expirationDate,
bestBidPrice: asNonZeroNumberOrUndefined(lastTickerInfo?.bidPx),
bestBidAmount: asNonZeroNumberOrUndefined(lastTickerInfo?.bidSz),
bestBidIV: asNonZeroNumberOrUndefined(summary.bidVol),
bestAskPrice: asNonZeroNumberOrUndefined(lastTickerInfo?.askPx),
bestAskAmount: asNonZeroNumberOrUndefined(lastTickerInfo?.askSz),
bestAskIV: asNonZeroNumberOrUndefined(summary.askVol),
lastPrice: asNonZeroNumberOrUndefined(lastTickerInfo?.last),
openInterest: lastOpenInterest,
markPrice: lastMarkPrice,
markIV: asNonZeroNumberOrUndefined(summary.markVol),
delta: asNonZeroNumberOrUndefined(summary.delta),
gamma: asNonZeroNumberOrUndefined(summary.gamma),
vega: asNonZeroNumberOrUndefined(summary.vega),
theta: asNonZeroNumberOrUndefined(summary.theta),
rho: undefined,
underlyingPrice: lastUnderlyingPrice,
underlyingIndex: summary.uly,
timestamp: new Date(Number(summary.ts)),
localTimestamp: localTimestamp
};
yield optionSummary;
}
}
}
}
//---
//V3 Okex API mappers
// https://www.okex.com/docs/en/#ws_swap-README
export class OkexTradesMapper {
_exchange;
_market;
constructor(_exchange, _market) {
this._exchange = _exchange;
this._market = _market;
}
canHandle(message) {
return message.table === `${this._market}/trade`;
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
return [
{
channel: `${this._market}/trade`,
symbols
}
];
}
*map(okexTradesMessage, localTimestamp) {
for (const okexTrade of okexTradesMessage.data) {
const symbol = okexTrade.instrument_id;
yield {
type: 'trade',
symbol,
exchange: this._exchange,
id: typeof okexTrade.trade_id === 'string' ? okexTrade.trade_id : undefined,
price: Number(okexTrade.price),
amount: okexTrade.qty !== undefined ? Number(okexTrade.qty) : Number(okexTrade.size),
side: okexTrade.side,
timestamp: new Date(okexTrade.timestamp),
localTimestamp: localTimestamp
};
}
}
}
const mapBookLevel = (level) => {
const price = Number(level[0]);
const amount = Number(level[1]);
return { price, amount };
};
export class OkexBookChangeMapper {
_exchange;
_market;
_canUseTickByTickChannel;
constructor(_exchange, _market, _canUseTickByTickChannel) {
this._exchange = _exchange;
this._market = _market;
this._canUseTickByTickChannel = _canUseTickByTickChannel;
}
canHandle(message) {
const channelSuffix = this._canUseTickByTickChannel ? 'depth_l2_tbt' : 'depth';
return message.table === `${this._market}/${channelSuffix}`;
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
if (this._canUseTickByTickChannel) {
return [
{
channel: `${this._market}/depth_l2_tbt`,
symbols
}
];
}
// subscribe to both book channels and in canHandle decide which one to use
// as one can subscribe to date range period that overlaps both when only depth channel has been available
// and when both were available (both depth and depth_l2_tbt)
return [
{
channel: `${this._market}/depth_l2_tbt`,
symbols
},
{
channel: `${this._market}/depth`,
symbols
}
];
}
*map(okexDepthDataMessage, localTimestamp) {
for (const message of okexDepthDataMessage.data) {
if (message.bids.length === 0 && message.asks.length === 0 && okexDepthDataMessage.action !== 'partial') {
continue;
}
const timestamp = new Date(message.timestamp);
if (timestamp.valueOf() === 0) {
continue;
}
yield {
type: 'book_change',
symbol: message.instrument_id,
exchange: this._exchange,
isSnapshot: okexDepthDataMessage.action === 'partial',
bids: message.bids.map(mapBookLevel),
asks: message.asks.map(mapBookLevel),
timestamp,
localTimestamp: localTimestamp
};
}
}
}
export class OkexDerivativeTickerMapper {
_exchange;
pendingTickerInfoHelper = new PendingTickerInfoHelper();
_futuresChannels = ['futures/ticker', 'futures/mark_price'];
_swapChannels = ['swap/ticker', 'swap/mark_price', 'swap/funding_rate'];
constructor(_exchange) {
this._exchange = _exchange;
}
canHandle(message) {
const channels = this._exchange === 'okex-futures' ? this._futuresChannels : this._swapChannels;
return channels.includes(message.table);
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
const channels = this._exchange === 'okex-futures' ? this._futuresChannels : this._swapChannels;
return channels.map((channel) => {
return {
channel,
symbols
};
});
}
*map(message, localTimestamp) {
for (const okexMessage of message.data) {
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(okexMessage.instrument_id, this._exchange);
if ('funding_rate' in okexMessage) {
pendingTickerInfo.updateFundingRate(Number(okexMessage.funding_rate));
pendingTickerInfo.updateFundingTimestamp(new Date(okexMessage.funding_time));
if (okexMessage.estimated_rate !== undefined) {
pendingTickerInfo.updatePredictedFundingRate(Number(okexMessage.estimated_rate));
}
}
if ('mark_price' in okexMessage) {
pendingTickerInfo.updateMarkPrice(Number(okexMessage.mark_price));
}
if ('open_interest' in okexMessage) {
const openInterest = Number(okexMessage.open_interest);
if (openInterest > 0) {
pendingTickerInfo.updateOpenInterest(Number(okexMessage.open_interest));
}
}
if ('last' in okexMessage) {
pendingTickerInfo.updateLastPrice(Number(okexMessage.last));
}
if (okexMessage.timestamp !== undefined) {
pendingTickerInfo.updateTimestamp(new Date(okexMessage.timestamp));
}
if (pendingTickerInfo.hasChanged()) {
yield pendingTickerInfo.getSnapshot(localTimestamp);
}
}
}
}
export class OkexOptionSummaryMapper {
_indexPrices = new Map();
expiration_regex = /(\d{2})(\d{2})(\d{2})/;
canHandle(message) {
return message.table === 'index/ticker' || message.table === 'option/summary';
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
const indexes = symbols !== undefined
? symbols.map((s) => {
const symbolParts = s.split('-');
return `${symbolParts[0]}-${symbolParts[1]}`;
})
: undefined;
return [
{
channel: `option/summary`,
symbols
},
{
channel: `index/ticker`,
symbols: indexes
}
];
}
*map(message, localTimestamp) {
if (message.table === 'index/ticker') {
for (const index of message.data) {
const lastIndexPrice = Number(index.last);
if (lastIndexPrice > 0) {
this._indexPrices.set(index.instrument_id, lastIndexPrice);
}
}
return;
}
for (const summary of message.data) {
const symbolParts = summary.instrument_id.split('-');
const isPut = symbolParts[4] === 'P';
const strikePrice = Number(symbolParts[3]);
var dateArray = this.expiration_regex.exec(symbolParts[2]);
const expirationDate = new Date(Date.UTC(+('20' + dateArray[1]), +dateArray[2] - 1, +dateArray[3], 8, 0, 0, 0));
const lastUnderlyingPrice = this._indexPrices.get(summary.underlying);
const optionSummary = {
type: 'option_summary',
symbol: summary.instrument_id,
exchange: 'okex-options',
optionType: isPut ? 'put' : 'call',
strikePrice,
expirationDate,
bestBidPrice: asNonZeroNumberOrUndefined(summary.best_bid),
bestBidAmount: asNonZeroNumberOrUndefined(summary.best_bid_size),
bestBidIV: asNonZeroNumberOrUndefined(summary.bid_vol),
bestAskPrice: asNonZeroNumberOrUndefined(summary.best_ask),
bestAskAmount: asNonZeroNumberOrUndefined(summary.best_ask_size),
bestAskIV: asNonZeroNumberOrUndefined(summary.ask_vol),
lastPrice: asNonZeroNumberOrUndefined(summary.last),
openInterest: asNonZeroNumberOrUndefined(summary.open_interest),
markPrice: asNonZeroNumberOrUndefined(summary.mark_price),
markIV: asNonZeroNumberOrUndefined(summary.mark_vol),
delta: asNonZeroNumberOrUndefined(summary.delta),
gamma: asNonZeroNumberOrUndefined(summary.gamma),
vega: asNonZeroNumberOrUndefined(summary.vega),
theta: asNonZeroNumberOrUndefined(summary.theta),
rho: undefined,
underlyingPrice: lastUnderlyingPrice,
underlyingIndex: summary.underlying,
timestamp: new Date(summary.timestamp),
localTimestamp: localTimestamp
};
yield optionSummary;
}
}
}
export class OkexLiquidationsMapper {
_exchange;
_market;
constructor(_exchange, _market) {
this._exchange = _exchange;
this._market = _market;
}
canHandle(message) {
return message.table === `${this._market}/liquidation`;
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
return [
{
channel: `${this._market}/liquidation`,
symbols
}
];
}
*map(okexLiquidationDataMessage, localTimestamp) {
for (const okexLiquidation of okexLiquidationDataMessage.data) {
const liquidation = {
type: 'liquidation',
symbol: okexLiquidation.instrument_id,
exchange: this._exchange,
id: undefined,
price: Number(okexLiquidation.price),
amount: Number(okexLiquidation.size),
side: okexLiquidation.type === '3' ? 'sell' : 'buy',
timestamp: new Date(okexLiquidation.created_at),
localTimestamp: localTimestamp
};
yield liquidation;
}
}
}
export class OkexBookTickerMapper {
_exchange;
_market;
constructor(_exchange, _market) {
this._exchange = _exchange;
this._market = _market;
}
canHandle(message) {
return message.table === `${this._market}/ticker`;
}
getFilters(symbols) {
symbols = upperCaseSymbols(symbols);
return [
{
channel: `${this._market}/ticker`,
symbols
}
];
}
*map(message, localTimestamp) {
for (const okexTicker of message.data) {
const ticker = {
type: 'book_ticker',
symbol: okexTicker.instrument_id,
exchange: this._exchange,
askAmount: asNonZeroNumberOrUndefined(okexTicker.best_ask_size),
askPrice: asNonZeroNumberOrUndefined(okexTicker.best_ask),
bidPrice: asNonZeroNumberOrUndefined(okexTicker.best_bid),
bidAmount: asNonZeroNumberOrUndefined(okexTicker.best_bid_size),
timestamp: new Date(okexTicker.timestamp),
localTimestamp: localTimestamp
};
yield ticker;
}
}
}
//# sourceMappingURL=okex.js.map