tardis-dev
Version:
Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js
187 lines • 7.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.computeBookSnapshots = void 0;
const handy_1 = require("../handy");
const orderbook_1 = require("../orderbook");
const computeBookSnapshots = (options) => () => new BookSnapshotComputable(options);
exports.computeBookSnapshots = computeBookSnapshots;
const emptyBookLevel = {
price: undefined,
amount: undefined
};
const levelsChanged = (level1, level2) => {
if (level1.amount !== level2.amount) {
return true;
}
if (level1.price !== level2.price) {
return true;
}
return false;
};
class BookSnapshotComputable {
constructor({ depth, name, interval, removeCrossedLevels, grouping, onCrossedLevelRemoved }) {
this.sourceDataTypes = ['book_change'];
this._bookChanged = false;
this._initialized = false;
this._type = 'book_snapshot';
this._lastUpdateTimestamp = new Date(-1);
this._bids = [];
this._asks = [];
this._getGroupedPriceForBids = (price) => {
const pow = Math.pow(10, this._groupingDecimalPlaces);
const pricePow = price * pow;
const groupPow = this._grouping * pow;
const remainder = (pricePow % groupPow) / pow;
return (pricePow - remainder * pow) / pow;
};
this._getGroupedPriceForAsks = (price) => {
const pow = Math.pow(10, this._groupingDecimalPlaces);
const pricePow = price * pow;
const groupPow = this._grouping * pow;
const remainder = (pricePow % groupPow) / pow;
return (pricePow - remainder * pow + (remainder > 0 ? groupPow : 0)) / pow;
};
this._depth = depth;
this._interval = interval;
this._grouping = grouping;
this._groupingDecimalPlaces = this._grouping ? (0, handy_1.decimalPlaces)(this._grouping) : undefined;
this._orderBook = new orderbook_1.OrderBook({
removeCrossedLevels,
onCrossedLevelRemoved
});
// initialize all bids/asks levels to empty ones
for (let i = 0; i < this._depth; i++) {
this._bids[i] = emptyBookLevel;
this._asks[i] = emptyBookLevel;
}
if (name === undefined) {
this._name = `${this._type}_${depth}${this._grouping ? `_grouped${this._grouping}` : ''}_${interval}ms`;
}
else {
this._name = name;
}
}
*compute(bookChange) {
if (this._hasNewSnapshot(bookChange.timestamp)) {
yield this._getSnapshot(bookChange);
}
this._update(bookChange);
// check again after the update as book snapshot with interval set to 0 (real-time) could have changed
// or it's initial snapshot
if (this._hasNewSnapshot(bookChange.timestamp)) {
yield this._getSnapshot(bookChange);
if (this._initialized === false) {
this._initialized = true;
}
}
}
_hasNewSnapshot(timestamp) {
if (this._bookChanged === false) {
return false;
}
// report new snapshot anytime book changed
if (this._interval === 0) {
return true;
}
// report new snapshot for book snapshots with interval for initial snapshot
if (this._initialized === false) {
return true;
}
const currentTimestampTimeBucket = this._getTimeBucket(timestamp);
const snapshotTimestampBucket = this._getTimeBucket(this._lastUpdateTimestamp);
if (currentTimestampTimeBucket > snapshotTimestampBucket) {
// set timestamp to end of snapshot 'interval' period
this._lastUpdateTimestamp = new Date((snapshotTimestampBucket + 1) * this._interval);
return true;
}
return false;
}
_update(bookChange) {
this._orderBook.update(bookChange);
if (this._grouping !== undefined) {
this._updateSideGrouped(this._orderBook.bids(), this._bids, this._getGroupedPriceForBids);
this._updateSideGrouped(this._orderBook.asks(), this._asks, this._getGroupedPriceForAsks);
}
else {
this._updatedNotGrouped();
}
this._lastUpdateTimestamp = bookChange.timestamp;
}
_updatedNotGrouped() {
const bidsIterable = this._orderBook.bids();
const asksIterable = this._orderBook.asks();
for (let i = 0; i < this._depth; i++) {
const bidLevelResult = bidsIterable.next();
const newBid = bidLevelResult.done ? emptyBookLevel : bidLevelResult.value;
if (levelsChanged(this._bids[i], newBid)) {
this._bids[i] = { ...newBid };
this._bookChanged = true;
}
const askLevelResult = asksIterable.next();
const newAsk = askLevelResult.done ? emptyBookLevel : askLevelResult.value;
if (levelsChanged(this._asks[i], newAsk)) {
this._asks[i] = { ...newAsk };
this._bookChanged = true;
}
}
}
_updateSideGrouped(newLevels, existingGroupedLevels, getGroupedPriceForLevel) {
let currentGroupedPrice = undefined;
let aggAmount = 0;
let currentDepth = 0;
for (const notGroupedLevel of newLevels) {
const groupedPrice = getGroupedPriceForLevel(notGroupedLevel.price);
if (currentGroupedPrice == undefined) {
currentGroupedPrice = groupedPrice;
}
if (currentGroupedPrice != groupedPrice) {
const groupedLevel = {
price: currentGroupedPrice,
amount: aggAmount
};
if (levelsChanged(existingGroupedLevels[currentDepth], groupedLevel)) {
existingGroupedLevels[currentDepth] = groupedLevel;
this._bookChanged = true;
}
currentDepth++;
if (currentDepth === this._depth) {
break;
}
currentGroupedPrice = groupedPrice;
aggAmount = 0;
}
aggAmount += notGroupedLevel.amount;
}
if (currentDepth < this._depth && aggAmount > 0) {
const groupedLevel = {
price: currentGroupedPrice,
amount: aggAmount
};
if (levelsChanged(existingGroupedLevels[currentDepth], groupedLevel)) {
existingGroupedLevels[currentDepth] = groupedLevel;
this._bookChanged = true;
}
}
}
_getSnapshot(bookChange) {
const snapshot = {
type: this._type,
symbol: bookChange.symbol,
exchange: bookChange.exchange,
name: this._name,
depth: this._depth,
interval: this._interval,
grouping: this._grouping,
bids: [...this._bids],
asks: [...this._asks],
timestamp: this._lastUpdateTimestamp,
localTimestamp: bookChange.localTimestamp
};
this._bookChanged = false;
return snapshot;
}
_getTimeBucket(timestamp) {
return Math.floor(timestamp.valueOf() / this._interval);
}
}
//# sourceMappingURL=booksnapshot.js.map