hft-js
Version:
High-Frequency Trading in Node.js
126 lines • 4.22 kB
JavaScript
/*
* bar.ts
*
* Copyright (c) 2025 Xiongfei Shi
*
* Author: Xiongfei Shi <xiongfei.shi(a)icloud.com>
* License: Apache-2.0
*
* https://github.com/shixiongfei/hft.js
*/
import { getBarVolume } from "./utils.js";
export class BarGenerator {
receivers;
symbol;
maxVolume;
shouldUpdate;
bar;
constructor(symbol, maxVolume = 0) {
this.receivers = [];
this.symbol = symbol;
this.maxVolume = maxVolume;
this.shouldUpdate = 0;
}
get receiverCount() {
return this.receivers.length;
}
addReceiver(receiver) {
if (!this.receivers.includes(receiver)) {
if (receiver.onUpdateBar) {
this.shouldUpdate += 1;
}
this.receivers.push(receiver);
}
}
removeReceiver(receiver) {
const index = this.receivers.indexOf(receiver);
if (index >= 0) {
if (receiver.onUpdateBar) {
this.shouldUpdate -= 1;
}
this.receivers.splice(index, 1);
}
}
onTick(tick, tape) {
if (tick.symbol !== this.symbol) {
return;
}
const date = tick.date;
const time = this.maxVolume > 0 ? tick.time : Math.floor(tick.time / 100) * 100;
if (this.bar) {
const isFinished = this.maxVolume > 0
? this.bar.volume >= this.maxVolume
: this.bar.date !== date || this.bar.time !== time;
if (isFinished) {
const bar = Object.freeze(this.bar);
Object.freeze(bar.buyVolumes);
Object.freeze(bar.sellVolumes);
this.receivers.forEach((receiver) => receiver.onBar(bar));
this.bar = undefined;
}
}
if (tape.volumeDelta === 0) {
return;
}
if (!this.bar) {
this.bar = this._createBar(date, time, tick);
}
this.bar.openInterest = tick.openInterest;
this.bar.closePrice = tick.lastPrice;
this.bar.highPrice = Math.max(this.bar.highPrice, tick.lastPrice);
this.bar.lowPrice = Math.min(this.bar.lowPrice, tick.lastPrice);
this.bar.volume += tape.volumeDelta;
this.bar.amount += tape.amountDelta;
switch (tape.direction) {
case "up":
this.bar.buyVolumes[tick.lastPrice] =
tape.volumeDelta + (this.bar.buyVolumes[tick.lastPrice] ?? 0);
this.bar.delta += tape.volumeDelta;
break;
case "down":
this.bar.sellVolumes[tick.lastPrice] =
tape.volumeDelta + (this.bar.sellVolumes[tick.lastPrice] ?? 0);
this.bar.delta -= tape.volumeDelta;
break;
}
if (tick.lastPrice !== this.bar.poc && tape.direction !== "none") {
const tickVP = getBarVolume(this.bar, tick.lastPrice);
const pocVP = getBarVolume(this.bar, this.bar.poc);
if (tickVP > pocVP) {
this.bar.poc = tick.lastPrice;
}
}
if (this.shouldUpdate > 0) {
const bar = Object.freeze({
...this.bar,
buyVolumes: Object.freeze({ ...this.bar.buyVolumes }),
sellVolumes: Object.freeze({ ...this.bar.sellVolumes }),
});
this.receivers.forEach((receiver) => {
if (receiver.onUpdateBar) {
receiver.onUpdateBar(bar, tick, tape);
}
});
}
}
_createBar(date, time, tick) {
return {
symbol: this.symbol,
date: date,
time: time,
openInterest: tick.openInterest,
openPrice: tick.lastPrice,
highPrice: tick.lastPrice,
lowPrice: tick.lastPrice,
closePrice: tick.lastPrice,
volume: 0,
amount: 0,
delta: 0,
poc: tick.lastPrice,
buyVolumes: {},
sellVolumes: {},
};
}
}
export const createBarGenerator = (symbol, maxVolume = 0) => new BarGenerator(symbol, maxVolume);
//# sourceMappingURL=bar.js.map