UNPKG

@jalmonter/tardis-dev

Version:

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

301 lines 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.fromMicroSecondsToDate = exports.lowerCaseSymbols = exports.upperCaseSymbols = exports.asNumberIfValid = exports.decimalPlaces = exports.CappedSet = exports.CircularBuffer = exports.cleanTempFiles = exports.optimizeFilters = exports.parseμs = exports.batch = exports.getFilters = exports.normalizeMessages = exports.take = exports.HttpError = exports.ONE_SEC_IN_MS = exports.sequence = exports.addDays = exports.addMinutes = exports.doubleDigit = exports.formatDateToPath = exports.wait = exports.parseAsUTCDate = void 0; function parseAsUTCDate(val) { // not sure about this one, but it should force parsing date as UTC date not as local timezone if (val.endsWith('Z') === false) { val += 'Z'; } var date = new Date(val); return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes())); } exports.parseAsUTCDate = parseAsUTCDate; function wait(delayMS) { return new Promise((resolve) => { setTimeout(resolve, delayMS); }); } exports.wait = wait; function formatDateToPath(date) { const year = date.getUTCFullYear(); const month = doubleDigit(date.getUTCMonth() + 1); const day = doubleDigit(date.getUTCDate()); const hour = doubleDigit(date.getUTCHours()); const minute = doubleDigit(date.getUTCMinutes()); return `${year}/${month}/${day}/${hour}/${minute}`; } exports.formatDateToPath = formatDateToPath; function doubleDigit(input) { return input < 10 ? '0' + input : '' + input; } exports.doubleDigit = doubleDigit; function addMinutes(date, minutes) { return new Date(date.getTime() + minutes * 60000); } exports.addMinutes = addMinutes; function addDays(date, days) { return new Date(date.getTime() + days * 60000 * 1440); } exports.addDays = addDays; function* sequence(end, seed = 0) { let current = seed; while (current < end) { yield current; current += 1; } return; } exports.sequence = sequence; exports.ONE_SEC_IN_MS = 1000; class HttpError extends Error { constructor(status, responseText, url) { super(`HttpError: status code: ${status}, response text: ${responseText}`); this.status = status; this.responseText = responseText; this.url = url; } } exports.HttpError = HttpError; function* take(iterable, length) { if (length === 0) { return; } for (const item of iterable) { yield item; length--; if (length === 0) { return; } } } exports.take = take; async function* normalizeMessages(exchange, messages, mappers, createMappers, withDisconnectMessages, filter, currentTimestamp) { let previousLocalTimestamp = currentTimestamp; let mappersForExchange = mappers; if (mappersForExchange.length === 0) { throw new Error(`Can't normalize data without any normalizers provided`); } for await (const messageWithTimestamp of messages) { if (messageWithTimestamp === undefined) { // we received undefined meaning Websocket disconnection // lets create new mappers with clean state for 'new connection' mappersForExchange = undefined; // if flag withDisconnectMessages is set, yield disconnect message if (withDisconnectMessages === true && previousLocalTimestamp !== undefined) { const disconnect = { type: 'disconnect', exchange, localTimestamp: previousLocalTimestamp }; yield disconnect; } continue; } if (mappersForExchange === undefined) { mappersForExchange = createMappers(messageWithTimestamp.localTimestamp); } previousLocalTimestamp = messageWithTimestamp.localTimestamp; for (const mapper of mappersForExchange) { if (mapper.canHandle(messageWithTimestamp.message)) { const mappedMessages = mapper.map(messageWithTimestamp.message, messageWithTimestamp.localTimestamp); if (!mappedMessages) { continue; } for (const message of mappedMessages) { if (filter === undefined) { yield message; } else if (filter(message.symbol)) { yield message; } } } } } } exports.normalizeMessages = normalizeMessages; function getFilters(mappers, symbols) { const filters = mappers.flatMap((mapper) => mapper.getFilters(symbols)); const deduplicatedFilters = filters.reduce((prev, current) => { const matchingExisting = prev.find((c) => c.channel === current.channel); if (matchingExisting !== undefined) { if (matchingExisting.symbols !== undefined && current.symbols) { for (let symbol of current.symbols) { if (matchingExisting.symbols.includes(symbol) === false) { matchingExisting.symbols.push(symbol); } } } else if (current.symbols) { matchingExisting.symbols = [...current.symbols]; } } else { prev.push(current); } return prev; }, []); return deduplicatedFilters; } exports.getFilters = getFilters; function* batch(symbols, batchSize) { for (let i = 0; i < symbols.length; i += batchSize) { yield symbols.slice(i, i + batchSize); } } exports.batch = batch; function parseμs(dateString) { // check if we have ISO 8601 format date string, e.g: 2019-06-01T00:03:03.1238784Z or 2020-07-22T00:09:16.836773Z // or 2020-03-01T00:00:24.893456+00:00 if (dateString.length === 27 || dateString.length === 28 || dateString.length === 32 || dateString.length === 30) { return Number(dateString.slice(23, 26)); } return 0; } exports.parseμs = parseμs; function optimizeFilters(filters) { // deduplicate filters (if the channel was provided multiple times) const optimizedFilters = filters.reduce((prev, current) => { const matchingExisting = prev.find((c) => c.channel === current.channel); if (matchingExisting) { // both previous and current have symbols let's merge them if (matchingExisting.symbols && current.symbols) { matchingExisting.symbols.push(...current.symbols); } else if (current.symbols) { matchingExisting.symbols = [...current.symbols]; } } else { prev.push(current); } return prev; }, []); // sort filters in place to improve local disk cache ratio (no matter filters order if the same filters are provided will hit the cache) optimizedFilters.sort((f1, f2) => { if (f1.channel < f2.channel) { return -1; } if (f1.channel > f2.channel) { return 1; } return 0; }); // sort and deduplicate filters symbols optimizedFilters.forEach((filter) => { if (filter.symbols) { filter.symbols = [...new Set(filter.symbols)].sort(); } }); return optimizedFilters; } exports.optimizeFilters = optimizeFilters; const tmpFileCleanups = new Map(); function cleanTempFiles() { tmpFileCleanups.forEach((cleanup) => cleanup()); } exports.cleanTempFiles = cleanTempFiles; class CircularBuffer { constructor(_bufferSize) { this._bufferSize = _bufferSize; this._buffer = []; this._index = 0; } append(value) { const isFull = this._buffer.length === this._bufferSize; let poppedValue; if (isFull) { poppedValue = this._buffer[this._index]; } this._buffer[this._index] = value; this._index = (this._index + 1) % this._bufferSize; return poppedValue; } *items() { for (let i = 0; i < this._buffer.length; i++) { const index = (this._index + i) % this._buffer.length; yield this._buffer[index]; } } get count() { return this._buffer.length; } clear() { this._buffer = []; this._index = 0; } } exports.CircularBuffer = CircularBuffer; class CappedSet { constructor(_maxSize) { this._maxSize = _maxSize; this._set = new Set(); } has(value) { return this._set.has(value); } add(value) { if (this._set.size >= this._maxSize) { this._set.delete(this._set.keys().next().value); } this._set.add(value); } remove(value) { this._set.delete(value); } size() { return this._set.size; } } exports.CappedSet = CappedSet; function hasFraction(n) { return Math.abs(Math.round(n) - n) > 1e-10; } // https://stackoverflow.com/a/44815797 function decimalPlaces(n) { let count = 0; // multiply by increasing powers of 10 until the fractional part is ~ 0 while (hasFraction(n * 10 ** count) && isFinite(10 ** count)) count++; return count; } exports.decimalPlaces = decimalPlaces; function asNumberIfValid(val) { if (val === undefined || val === null) { return; } var asNumber = Number(val); if (isNaN(asNumber) || isFinite(asNumber) === false) { return; } if (asNumber === 0) { return; } return asNumber; } exports.asNumberIfValid = asNumberIfValid; function upperCaseSymbols(symbols) { if (symbols !== undefined) { return symbols.map((s) => s.toUpperCase()); } return; } exports.upperCaseSymbols = upperCaseSymbols; function lowerCaseSymbols(symbols) { if (symbols !== undefined) { return symbols.map((s) => s.toLowerCase()); } return; } exports.lowerCaseSymbols = lowerCaseSymbols; const fromMicroSecondsToDate = (micros) => { const isMicroseconds = micros > 1e15; // Check if the number is likely in microseconds if (!isMicroseconds) { return new Date(micros); } const timestamp = new Date(micros / 1000); timestamp.μs = micros % 1000; return timestamp; }; exports.fromMicroSecondsToDate = fromMicroSecondsToDate; //# sourceMappingURL=handy.js.map