UNPKG

hft-js

Version:

High-Frequency Trading in Node.js

126 lines 4.22 kB
/* * 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