일목균형표 수정

This commit is contained in:
Macbook
2026-05-27 23:36:48 +09:00
parent 9cee6387c3
commit 8cc0d1c88c
73 changed files with 2256 additions and 334 deletions
+29 -2
View File
@@ -51,6 +51,8 @@ import CandlePaneControls from './CandlePaneControls';
import { getKoreanName } from '../utils/marketNameCache';
import { DEFAULT_DISPLAY_TIMEZONE } from '../utils/timezone';
import { classifyIndicatorChartChange, singleIndParamKey } from '../utils/indicatorChartSync';
import type { ChartPaneSeparatorOptions } from '../types/chartPaneSeparator';
import PaneSeparatorOverlay from './PaneSeparatorOverlay';
interface TradingChartProps {
bars: OHLCVBar[];
@@ -126,6 +128,8 @@ interface TradingChartProps {
seriesPriceLabelsEnabled?: boolean;
/** 실시간 차트 화면 표시 여부 — false 이면 숨김 중 무거운 재로드 지연 */
chartVisible?: boolean;
/** pane(캔들·거래량·보조지표) 구분선 */
paneSeparatorOptions?: ChartPaneSeparatorOptions;
}
const TradingChart: React.FC<TradingChartProps> = ({
@@ -156,6 +160,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
showHoverToolbar = true,
seriesPriceLabelsEnabled = true,
chartVisible = true,
paneSeparatorOptions,
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null); // 스크롤 래퍼
@@ -207,6 +212,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
const prevSortedPKRef = useRef<string>(''); // 순서 무관 paramKey (reorder 감지용)
const prevIndicatorsListRef = useRef<IndicatorConfig[]>(indicators);
const indicatorSyncInFlightRef = useRef(false);
const indicatorReloadGenRef = useRef(0);
const prevMarket = useRef<string>(market);
const prevMarketTf = useRef<Timeframe>(timeframe);
const lastAppliedMarketRef = useRef<string>('');
@@ -281,6 +287,11 @@ const TradingChart: React.FC<TradingChartProps> = ({
managerRef.current?.setSeriesPriceLabelsEnabled(seriesPriceLabelsEnabled);
}, [seriesPriceLabelsEnabled]);
useEffect(() => {
if (!paneSeparatorOptions) return;
managerRef.current?.setPaneSeparatorOptions(paneSeparatorOptions);
}, [paneSeparatorOptions]);
/**
* pane 높이 재배분 + 스크롤 컨테이너 확장
*
@@ -409,18 +420,27 @@ const TradingChart: React.FC<TradingChartProps> = ({
) => {
const cover = createIndicatorReloadCover(containerRef.current);
const savedLR = mgr.getVisibleLogicalRange();
const reloadGen = ++indicatorReloadGenRef.current;
indicatorSyncInFlightRef.current = true;
void mgr.reloadIndicatorsOnly(inds).then(() => {
if (reloadGen !== indicatorReloadGenRef.current) {
fadeOutIndicatorReloadCover(cover);
return;
}
afterIndicatorPaneMutation(mgr);
requestAnimationFrame(() => requestAnimationFrame(() => {
if (reloadGen !== indicatorReloadGenRef.current) {
fadeOutIndicatorReloadCover(cover);
return;
}
restoreLogicalRange(mgr, savedLR);
fadeOutIndicatorReloadCover(cover);
indicatorSyncInFlightRef.current = false;
onComplete?.();
}));
});
}, [applyPaneLayout, afterIndicatorPaneMutation, restoreLogicalRange]);
}, [afterIndicatorPaneMutation, restoreLogicalRange]);
const queueFullReload = useCallback(() => {
if (!chartVisibleRef.current) return;
@@ -635,6 +655,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
mgr.setVolumeVisible(false, wrapperH > 0 ? wrapperH : undefined);
}
mgr.setSeriesPriceLabelsEnabled(seriesPriceLabelsEnabled);
if (paneSeparatorOptions) mgr.setPaneSeparatorOptions(paneSeparatorOptions);
onManagerReady(mgr);
const unsub = mgr.subscribeCrosshair((p: MouseEventParams<Time>) => {
@@ -1251,6 +1272,12 @@ const TradingChart: React.FC<TradingChartProps> = ({
onClose={() => setCtxMenu(null)}
/>
)}
{chartMgr && paneSeparatorOptions && (
<PaneSeparatorOverlay
manager={chartMgr}
options={paneSeparatorOptions}
/>
)}
{chartMgr && (
<DrawingCanvas
manager={chartMgr}
@@ -1403,7 +1430,7 @@ function sortedParamKey(inds: IndicatorConfig[]): string {
*/
function styleKey(inds: IndicatorConfig[]): string {
return inds.map(i =>
`${i.id}|${i.hidden ? '1' : '0'}|${i.lastValueVisible === false ? '0' : '1'}|${(i.plots ?? []).map(p => `${p.color ?? ''}:${p.lineWidth ?? 1}:${p.lineStyle ?? 'solid'}`).join(',')}|${JSON.stringify(i.plotVisibility ?? {})}|${JSON.stringify((i.hlines ?? []).map(h => `${h.price}:${h.color}:${h.visible ?? true}:${h.lineStyle ?? 'dashed'}:${h.lineWidth ?? 1}`))}|${JSON.stringify(i.cloudColors ?? {})}`
`${i.id}|${i.hidden ? '1' : '0'}|${i.lastValueVisible === false ? '0' : '1'}|${(i.plots ?? []).map(p => `${p.color ?? ''}:${p.lineWidth ?? 1}:${p.lineStyle ?? 'solid'}:${p.histogramUpColor ?? ''}:${p.histogramDownColor ?? ''}`).join(',')}|${JSON.stringify(i.plotVisibility ?? {})}|${JSON.stringify((i.hlines ?? []).map(h => `${h.price}:${h.color}:${h.visible ?? true}:${h.lineStyle ?? 'dashed'}:${h.lineWidth ?? 1}`))}|${JSON.stringify(i.cloudColors ?? {})}`
).join(';');
}