백테스팅 적용
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
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<Props> = ({ 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 (
|
||||
<div className="btd-osc-stack">
|
||||
<div className="btd-osc-pane">
|
||||
<div className="btd-osc-head">
|
||||
<span className="btd-osc-label">RSI (14)</span>
|
||||
<span className="btd-osc-val">{rsiLast != null ? rsiLast.toFixed(1) : '–'}</span>
|
||||
</div>
|
||||
<svg className="btd-osc-svg" viewBox="0 0 280 52" preserveAspectRatio="none">
|
||||
<line x1="0" y1="15" x2="280" y2="15" className="btd-osc-ref btd-osc-ref--hi" />
|
||||
<line x1="0" y1="26" x2="280" y2="26" className="btd-osc-ref" />
|
||||
<line x1="0" y1="37" x2="280" y2="37" className="btd-osc-ref btd-osc-ref--lo" />
|
||||
<path d={rsiPath} className="btd-osc-line btd-osc-line--rsi" fill="none" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="btd-osc-pane">
|
||||
<div className="btd-osc-head">
|
||||
<span className="btd-osc-label">MACD (12, 26, 9)</span>
|
||||
<span className="btd-osc-val">{macdLast != null ? macdLast.toFixed(2) : '–'}</span>
|
||||
</div>
|
||||
<svg className="btd-osc-svg" viewBox="0 0 280 52" preserveAspectRatio="none">
|
||||
<line x1="0" y1="26" x2="280" y2="26" className="btd-osc-ref" />
|
||||
<path d={macdPath} className="btd-osc-line btd-osc-line--macd" fill="none" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BacktestOscillatorPanes;
|
||||
Reference in New Issue
Block a user