goldenChat base source add
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
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 })),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user