투자관리 차트탭 기능 수정

This commit is contained in:
Macbook
2026-05-31 16:31:25 +09:00
parent 1b7c39e11f
commit 78f00dbaa5
14 changed files with 935 additions and 111 deletions
@@ -0,0 +1,96 @@
import React, { useMemo } from 'react';
import type { OHLCVBar } from '../../types';
import type { StrategyDto } from '../../utils/backendApi';
import {
buildOscillatorPaneData,
collectStrategyOscillatorPanes,
defaultOscillatorPaneSpecs,
} from '../../utils/strategyOscillatorSeries';
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(' ');
}
interface Props {
bars: OHLCVBar[];
strategy?: StrategyDto;
/** 부모 flex 영역 높이에 맞춰 pane 균등 분배 */
fillHeight?: boolean;
}
/** 전략 조건에 사용된 보조지표 하단 pane (없으면 RSI·MACD 기본) */
const StrategyOscillatorPanes: React.FC<Props> = ({ bars, strategy, fillHeight = false }) => {
const panes = useMemo(() => {
const specs = collectStrategyOscillatorPanes(strategy);
const useSpecs = specs.length > 0 ? specs : defaultOscillatorPaneSpecs();
return buildOscillatorPaneData(bars, useSpecs);
}, [bars, strategy]);
if (panes.length === 0) return null;
return (
<div className={`btd-osc-stack${fillHeight ? ' btd-osc-stack--fill' : ''}`}>
{panes.map(pane => {
const last = pane.values[pane.values.length - 1];
const lo = pane.refLines?.[0];
const hi = pane.refLines?.[pane.refLines.length - 1];
const path = spark(
pane.values,
280,
52,
lo != null && hi != null && pane.spec.registryType === 'RSI' ? 0 : undefined,
lo != null && hi != null && pane.spec.registryType === 'RSI' ? 100 : undefined,
);
return (
<div key={pane.spec.key} className={`btd-osc-pane${fillHeight ? ' btd-osc-pane--fill' : ''}`}>
<div className="btd-osc-head">
<span className="btd-osc-label">{pane.spec.label}</span>
<span className="btd-osc-val">
{last != null
? (pane.spec.registryType === 'RSI' ? last.toFixed(1) : last.toFixed(2))
: ''}
</span>
</div>
<svg className="btd-osc-svg" viewBox="0 0 280 52" preserveAspectRatio="none">
{pane.refLines?.map((r, i) => {
const min = pane.refLines![0];
const max = pane.refLines![pane.refLines!.length - 1];
const y = 52 - ((r - min) / (max - min || 1)) * 44 - 4;
return (
<line
key={i}
x1="0"
y1={y}
x2="280"
y2={y}
className={`btd-osc-ref${i === 0 || i === pane.refLines!.length - 1 ? ' btd-osc-ref--hi' : ''}`}
/>
);
})}
{!pane.refLines?.length && (
<line x1="0" y1="26" x2="280" y2="26" className="btd-osc-ref" />
)}
<path
d={path}
className="btd-osc-line"
fill="none"
stroke={pane.color}
strokeWidth="1.5"
/>
</svg>
</div>
);
})}
</div>
);
};
export default StrategyOscillatorPanes;