| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- export interface VolatilityEstimatorOptions {
- windowMinutes?: number;
- minSamples?: number;
- maxCadenceMs?: number;
- }
- interface PricePoint {
- ts: number;
- price: number;
- }
- /**
- * Estimates short-term volatility based on rolling mid-price snapshots.
- */
- export class VolatilityEstimator {
- private readonly windowMinutes: number;
- private readonly minSamples: number;
- private readonly maxCadenceMs: number;
- private readonly history: PricePoint[] = [];
- constructor(options: VolatilityEstimatorOptions = {}) {
- this.windowMinutes = options.windowMinutes ?? 30;
- this.minSamples = options.minSamples ?? 10;
- this.maxCadenceMs = options.maxCadenceMs ?? 5_000;
- }
- update(price: number, ts: number = Date.now()): void {
- if (!Number.isFinite(price) || price <= 0) return;
- const last = this.history[this.history.length - 1];
- if (last && ts - last.ts < this.maxCadenceMs) {
- this.history[this.history.length - 1] = { ts, price };
- } else {
- this.history.push({ ts, price });
- }
- const cutoff = ts - this.windowMinutes * 60 * 1000;
- while (this.history.length > 0 && this.history[0]!.ts < cutoff) {
- this.history.shift();
- }
- }
- getAnnualizedVolatility(): number | undefined {
- if (this.history.length < this.minSamples) return undefined;
- const returns = this.computeLogReturns(this.history);
- if (returns.length === 0) return undefined;
- const stdDev = this.standardDeviation(returns);
- const periodsPerYear = (365 * 24 * 60) / this.windowMinutes;
- return stdDev * Math.sqrt(periodsPerYear);
- }
- getHourlyVolatilityBps(): number | undefined {
- if (this.history.length < 2) return undefined;
- const windowMs = 60 * 60 * 1000;
- const latest = this.history[this.history.length - 1]!;
- const cutoff = latest.ts - windowMs;
- const relevant = this.history.filter(point => point.ts >= cutoff);
- if (relevant.length < 2) return undefined;
- const first = relevant[0]!.price;
- const last = relevant[relevant.length - 1]!.price;
- const range = Math.abs(last - first) / first;
- return range * 10_000;
- }
- getStatus() {
- const oldest = this.history[0];
- const latest = this.history[this.history.length - 1];
- return {
- historySize: this.history.length,
- oldestTs: oldest?.ts,
- latestPrice: latest?.price,
- annualizedVol: this.getAnnualizedVolatility(),
- hourlyVolBps: this.getHourlyVolatilityBps()
- };
- }
- private computeLogReturns(series: PricePoint[]): number[] {
- const returns: number[] = [];
- for (let i = 1; i < series.length; i += 1) {
- const prev = series[i - 1]!.price;
- const curr = series[i]!.price;
- if (prev <= 0 || curr <= 0) continue;
- returns.push(Math.log(curr / prev));
- }
- return returns;
- }
- private standardDeviation(values: number[]): number {
- if (values.length === 0) return 0;
- const mean = values.reduce((sum, v) => sum + v, 0) / values.length;
- const variance =
- values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / values.length;
- return Math.sqrt(variance);
- }
- }
|