tardis-dev
Version:
Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js
126 lines • 4.56 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.combine = void 0;
const stream_1 = require("stream");
const events_1 = require("events");
const DATE_MAX = new Date(8640000000000000);
async function nextWithIndex(iterator, index) {
if ('offsetMS' in iterator) {
const result = await iterator.stream.next();
if (!result.done) {
const offsetMS = typeof iterator.offsetMS === 'function' ? iterator.offsetMS(result.value) : iterator.offsetMS;
if (offsetMS !== 0) {
result.value.localTimestamp.setUTCMilliseconds(result.value.localTimestamp.getUTCMilliseconds() + offsetMS);
}
}
return {
result,
index
};
}
else {
const result = await iterator.next();
return {
result,
index
};
}
}
function findOldestResult(oldest, current) {
if (oldest.result.done) {
return oldest;
}
if (current.result.done) {
return current;
}
const currentTimestamp = current.result.value.localTimestamp.valueOf();
const oldestTimestamp = oldest.result.value.localTimestamp.valueOf();
if (currentTimestamp < oldestTimestamp) {
return current;
}
if (currentTimestamp === oldestTimestamp) {
const currentTimestampMicroSeconds = current.result.value.localTimestamp.μs || 0;
const oldestTimestampMicroSeconds = oldest.result.value.localTimestamp.μs || 0;
if (currentTimestampMicroSeconds < oldestTimestampMicroSeconds) {
return current;
}
}
return oldest;
}
// combines multiple iterators from for example multiple exchanges
// works both for real-time and historical data
async function* combine(...iteratorsPayload) {
const iterators = iteratorsPayload.map((payload) => {
if ('stream' in payload) {
return payload.stream;
}
return payload;
});
if (iterators.length === 0) {
return;
}
// decide based on first provided iterator if we're dealing with real-time or historical data streams
if (iterators[0].__realtime__) {
const combinedStream = new stream_1.PassThrough({
objectMode: true,
highWaterMark: 8096
});
combinedStream.setMaxListeners(iterators.length + 1);
iterators.forEach(async function writeMessagesToCombinedStream(messages) {
for await (const message of messages) {
if (combinedStream.destroyed) {
return;
}
if (!combinedStream.write(message)) {
// Handle backpressure on write
await (0, events_1.once)(combinedStream, 'drain');
}
}
});
for await (const message of combinedStream) {
yield message;
}
}
else {
return yield* combineHistorical(iteratorsPayload);
}
}
exports.combine = combine;
async function* combineHistorical(iterators) {
try {
// wait for all results to resolve
const results = await Promise.all(iterators.map(nextWithIndex));
let aliveIteratorsCount = results.length;
do {
// if we're dealing with historical data replay
// and need to return combined messages iterable sorted by local timestamp in ascending order
// find resolved one that is the 'oldest'
const oldestResult = results.reduce(findOldestResult, results[0]);
const { result, index } = oldestResult;
if (result.done) {
aliveIteratorsCount--;
// we don't want finished iterators to every be considered 'oldest' again
// hence provide them with result that has local timestamp set to DATE_MAX
// and that is not done
results[index].result = {
done: false,
value: {
localTimestamp: DATE_MAX
}
};
}
else {
// yield oldest value and replace with next value from iterable for given index
yield result.value;
results[index] = await nextWithIndex(iterators[index], index);
}
} while (aliveIteratorsCount > 0);
}
finally {
for (let iterator of iterators) {
;
iterator.return();
}
}
}
//# sourceMappingURL=combine.js.map