import React, { useMemo } from 'react'; import type { OHLCVBar } from '../../types'; function spark(values: number[], w: number, h: number, min?: number, max?: number): string { if (!values.length) return ''; const lo = min ?? Math.min(...values); const hi = max ?? Math.max(...values); const range = hi - lo || 1; return values.map((v, i) => { const x = (i / Math.max(values.length - 1, 1)) * w; const y = h - ((v - lo) / range) * (h - 6) - 3; return `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`; }).join(' '); } function computeRsi(closes: number[], period = 14): number[] { if (closes.length < period + 1) return []; const out: number[] = []; let avgGain = 0; let avgLoss = 0; for (let i = 1; i <= period; i++) { const d = closes[i] - closes[i - 1]; if (d >= 0) avgGain += d; else avgLoss -= d; } avgGain /= period; avgLoss /= period; for (let i = period; i < closes.length; i++) { const d = closes[i] - closes[i - 1]; const gain = d > 0 ? d : 0; const loss = d < 0 ? -d : 0; avgGain = (avgGain * (period - 1) + gain) / period; avgLoss = (avgLoss * (period - 1) + loss) / period; const rs = avgLoss === 0 ? 100 : avgGain / avgLoss; out.push(100 - 100 / (1 + rs)); } return out; } function computeMacd(closes: number[]): number[] { const ema = (arr: number[], p: number) => { const k = 2 / (p + 1); let v = arr[0]; return arr.map((x, i) => (i === 0 ? x : (v = x * k + v * (1 - k)))); }; const e12 = ema(closes, 12); const e26 = ema(closes, 26); return e12.map((v, i) => v - e26[i]); } interface Props { bars: OHLCVBar[]; } /** 백테스팅 분석 — RSI / MACD 하단 pane (참조 UI) */ const BacktestOscillatorPanes: React.FC = ({ bars }) => { const { rsiPath, macdPath, rsiLast, macdLast } = useMemo(() => { const closes = bars.map(b => b.close); const rsi = computeRsi(closes).slice(-80); const macd = computeMacd(closes).slice(-80); return { rsiPath: spark(rsi, 280, 52, 0, 100), macdPath: spark(macd, 280, 52), rsiLast: rsi[rsi.length - 1], macdLast: macd[macd.length - 1], }; }, [bars]); if (bars.length < 20) return null; return (
RSI (14) {rsiLast != null ? rsiLast.toFixed(1) : '–'}
MACD (12, 26, 9) {macdLast != null ? macdLast.toFixed(2) : '–'}
); }; export default BacktestOscillatorPanes;