import type { OHLCVBar } from '../types'; export interface PriceStats { current: number; change: number; changePct: number; high52w: number; low52w: number; avgVol: number; avgRange: number; volatility: number; // annualized std dev of returns (%) rrsi14: number; // rough RSI(14) approximation pivotH: number; // classic pivot points pivotL: number; pivotP: number; support1: number; support2: number; resistance1: number; resistance2: number; trend: 'up' | 'down' | 'sideways'; } export function calcStats(bars: OHLCVBar[]): PriceStats { if (bars.length < 2) { return { current: 0, change: 0, changePct: 0, high52w: 0, low52w: 0, avgVol: 0, avgRange: 0, volatility: 0, rrsi14: 50, pivotH: 0, pivotL: 0, pivotP: 0, support1: 0, support2: 0, resistance1: 0, resistance2: 0, trend: 'sideways', }; } const last = bars[bars.length - 1]; const prev = bars[bars.length - 2]; const change = last.close - prev.close; const changePct = change / prev.close * 100; const lookback = Math.min(bars.length, 252); const slice = bars.slice(-lookback); const high52w = Math.max(...slice.map(b => b.high)); const low52w = Math.min(...slice.map(b => b.low)); const avgVol = slice.reduce((s, b) => s + b.volume, 0) / slice.length; const avgRange= slice.reduce((s, b) => s + (b.high - b.low), 0) / slice.length; // Annualized volatility const returns = slice.slice(1).map((b, i) => Math.log(b.close / slice[i].close)); const mean = returns.reduce((s, r) => s + r, 0) / returns.length; const variance = returns.reduce((s, r) => s + (r - mean) ** 2, 0) / returns.length; const volatility = Math.sqrt(variance * 252) * 100; // Rough RSI(14) const rsiSlice = bars.slice(-15); let gains = 0, losses = 0; for (let i = 1; i < rsiSlice.length; i++) { const d = rsiSlice[i].close - rsiSlice[i - 1].close; if (d > 0) gains += d; else losses -= d; } const avgGain = gains / 14; const avgLoss = losses / 14; const rrsi14 = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss); // Classic Pivot Points (from last bar) const pivotH = last.high; const pivotL = last.low; const pivotP = (last.high + last.low + last.close) / 3; const resistance1 = 2 * pivotP - last.low; const support1 = 2 * pivotP - last.high; const resistance2 = pivotP + (last.high - last.low); const support2 = pivotP - (last.high - last.low); // Trend (20-bar EMA vs 50-bar EMA) const ema20 = calcEMA(bars.map(b => b.close), 20); const ema50 = calcEMA(bars.map(b => b.close), 50); const e20 = ema20[ema20.length - 1]; const e50 = ema50[ema50.length - 1]; const trend = e20 > e50 * 1.002 ? 'up' : e20 < e50 * 0.998 ? 'down' : 'sideways'; return { current: last.close, change, changePct, high52w, low52w, avgVol, avgRange, volatility, rrsi14, pivotH, pivotL, pivotP, support1, support2, resistance1, resistance2, trend, }; } function calcEMA(prices: number[], period: number): number[] { const k = 2 / (period + 1); const result: number[] = []; let ema = prices[0]; for (const p of prices) { ema = p * k + ema * (1 - k); result.push(ema); } return result; } /** Auto-calculate Fibonacci retracement levels from visible data */ export function calcAutoFib(bars: OHLCVBar[], lookback = 50): { high: number; low: number; highTime: number; lowTime: number; levels: Array<{ ratio: number; price: number; label: string }>; } { const slice = bars.slice(-lookback); let high = -Infinity, low = Infinity, highTime = 0, lowTime = 0; for (const b of slice) { if (b.high > high) { high = b.high; highTime = b.time; } if (b.low < low) { low = b.low; lowTime = b.time; } } const range = high - low; const LEVELS = [ { ratio: 0, label: '0%' }, { ratio: 0.236, label: '23.6%' }, { ratio: 0.382, label: '38.2%' }, { ratio: 0.5, label: '50%' }, { ratio: 0.618, label: '61.8%' }, { ratio: 0.786, label: '78.6%' }, { ratio: 1, label: '100%' }, { ratio: 1.272, label: '127.2%' }, { ratio: 1.618, label: '161.8%' }, ]; return { high, low, highTime, lowTime, levels: LEVELS.map(l => ({ ...l, price: high - range * l.ratio })), }; }