가상매매 차트 오류 수정

This commit is contained in:
Macbook
2026-05-28 20:57:04 +09:00
parent 15160f7d2c
commit 9221b8dea2
3 changed files with 79 additions and 19 deletions
@@ -16,6 +16,7 @@ import { useHistoryLoader } from '../../hooks/useHistoryLoader';
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
import type { ChartManager } from '../../utils/ChartManager';
import {
buildVirtualTradingCardChartIndicators,
buildVirtualTradingChartIndicators,
resolveStrategyPrimaryTimeframe,
resolveStrategyTimeframes,
@@ -29,7 +30,6 @@ interface Props {
theme?: Theme;
running?: boolean;
chartRealtimeSource?: ChartRealtimeSource;
/** 차트 설정 — 지표 가격축 라벨·설명 */
chartSeriesPriceLabels?: boolean;
chartPaneSeparator?: import('../../types/chartPaneSeparator').ChartPaneSeparatorOptions;
/** 전체보기 분할 pane — 캔버스 높이 100% */
@@ -65,10 +65,11 @@ const VirtualTargetCardChart: React.FC<Props> = ({
setTimeframe(resolveStrategyPrimaryTimeframe(strategy));
}, [strategy, market, chartTimeframe]);
const indicators = useMemo(
() => buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig),
[strategy, getParams, getVisualConfig],
);
const indicators = useMemo(() => (
fillHeight
? buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig)
: buildVirtualTradingCardChartIndicators(strategy, getParams, getVisualConfig)
), [strategy, getParams, getVisualConfig, fillHeight]);
const effectiveIndicators = useMemo(
() => indicators.filter(ind => ind.timeframeVisibility?.[timeframe] !== false),
@@ -92,6 +93,10 @@ const VirtualTargetCardChart: React.FC<Props> = ({
if (!pending || !mgr || barsMarketRef.current !== marketRef.current) return;
pendingBarRef.current = null;
mgr.updateBar(pending);
const wrap = canvasWrapRef.current;
if (wrap && wrap.clientHeight > 0) {
mgr.resetPaneHeights(wrap.clientHeight);
}
}, []);
const applyRealtimeBar = useCallback((bar: OHLCVBar, append: boolean) => {
@@ -134,6 +139,20 @@ const VirtualTargetCardChart: React.FC<Props> = ({
barsMarketRef.current = barsMarket;
const barsReady = bars.length >= 2 && barsMarket === market;
const applyPaneHeights = useCallback(() => {
const wrap = canvasWrapRef.current;
const mgr = managerRef.current;
if (!wrap || wrap.clientHeight <= 0 || !mgr?.hasMainSeries()) return;
mgr.resetPaneHeights(wrap.clientHeight);
}, []);
const requestChartReload = useCallback(() => {
chartReloadTriggeredRef.current = true;
setChartReloadTick(t => t + 1);
}, []);
useLayoutEffect(() => {
chartLiveReadyRef.current = false;
pendingBarRef.current = null;
@@ -141,23 +160,41 @@ const VirtualTargetCardChart: React.FC<Props> = ({
}, [market, timeframe]);
useEffect(() => {
const timers = [100, 200, 500, 1000, 2000].map(delay =>
const wrap = canvasWrapRef.current;
if (!wrap) return;
const timers = [100, 300, 800, 1500, 2500].map(delay =>
setTimeout(() => {
const wrap = canvasWrapRef.current;
const mgr = managerRef.current;
if (!wrap) return;
const h = wrap.clientHeight;
if (!canvasWrapRef.current) return;
const h = canvasWrapRef.current.clientHeight;
if (h <= 0) return;
if (mgr?.hasMainSeries()) {
mgr.resetPaneHeights(h);
} else if (!chartReloadTriggeredRef.current && delay >= 1000 && bars.length > 0) {
chartReloadTriggeredRef.current = true;
setChartReloadTick(t => t + 1);
if (managerRef.current?.hasMainSeries()) {
managerRef.current.resetPaneHeights(h);
} else if (!chartReloadTriggeredRef.current && delay >= 800 && barsReady) {
requestChartReload();
}
}, delay),
);
return () => timers.forEach(clearTimeout);
}, [market, timeframe, bars.length]);
const ro = new ResizeObserver(() => applyPaneHeights());
ro.observe(wrap);
return () => {
timers.forEach(clearTimeout);
ro.disconnect();
};
}, [market, timeframe, barsReady, applyPaneHeights, requestChartReload]);
useEffect(() => {
if (!barsReady) return;
if (managerRef.current?.hasMainSeries()) {
applyPaneHeights();
return;
}
if (!chartReloadTriggeredRef.current) {
requestChartReload();
}
}, [barsReady, applyPaneHeights, requestChartReload]);
useEffect(() => {
if (!useUpbit) return;
@@ -195,8 +232,12 @@ const VirtualTargetCardChart: React.FC<Props> = ({
<span> </span>
</div>
)}
{!isLoading && !barsReady && (
<div className="vtd-card-chart-loading"> </div>
)}
<TradingChart
key={`${market}-${timeframe}-${chartReloadTick}`}
chartVisible
bars={bars}
barsMarket={barsMarket}
market={market}
@@ -210,7 +251,6 @@ const VirtualTargetCardChart: React.FC<Props> = ({
logScale={false}
drawingsLocked
drawingsVisible={false}
displayTimezone={DEFAULT_DISPLAY_TIMEZONE}
onCrosshair={noop as (d: LegendData | null) => void}
onManagerReady={mgr => { managerRef.current = mgr; }}
onAddDrawing={noop as (d: Drawing) => void}
@@ -1313,8 +1313,19 @@
color: var(--accent, #3f7ef5);
}
.vtd-card--chart-mode {
.vtd-card.vtd-card--chart-mode {
min-height: 420px;
align-self: stretch;
}
.vtd-card.vtd-card--chart-mode > .vtd-card-chart-panel {
flex: 1;
min-height: 0;
}
.vtd-grid:has(.vtd-card--chart-mode) {
grid-auto-rows: minmax(440px, auto);
align-items: stretch;
}
.vtd-card-chart-panel {
@@ -266,3 +266,12 @@ export function buildVirtualTradingChartIndicators(
if (!defaultIchimoku) return strategyIndicators;
return [defaultIchimoku, ...strategyIndicators];
}
/** 가상매매 카드 인라인 차트 — 캔들 영역 확보(일목·다중 지표 pane 제외, 전략 지표 최대 1개) */
export function buildVirtualTradingCardChartIndicators(
strategy: StrategyDto | undefined,
getParams: GetParams,
getVisual: GetVisual,
): IndicatorConfig[] {
return buildStrategyChartIndicators(strategy, getParams, getVisual).slice(0, 1);
}