가상매매 차트 오류 수정
This commit is contained in:
@@ -16,6 +16,7 @@ import { useHistoryLoader } from '../../hooks/useHistoryLoader';
|
|||||||
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
|
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
|
||||||
import type { ChartManager } from '../../utils/ChartManager';
|
import type { ChartManager } from '../../utils/ChartManager';
|
||||||
import {
|
import {
|
||||||
|
buildVirtualTradingCardChartIndicators,
|
||||||
buildVirtualTradingChartIndicators,
|
buildVirtualTradingChartIndicators,
|
||||||
resolveStrategyPrimaryTimeframe,
|
resolveStrategyPrimaryTimeframe,
|
||||||
resolveStrategyTimeframes,
|
resolveStrategyTimeframes,
|
||||||
@@ -29,7 +30,6 @@ interface Props {
|
|||||||
theme?: Theme;
|
theme?: Theme;
|
||||||
running?: boolean;
|
running?: boolean;
|
||||||
chartRealtimeSource?: ChartRealtimeSource;
|
chartRealtimeSource?: ChartRealtimeSource;
|
||||||
/** 차트 설정 — 지표 가격축 라벨·설명 */
|
|
||||||
chartSeriesPriceLabels?: boolean;
|
chartSeriesPriceLabels?: boolean;
|
||||||
chartPaneSeparator?: import('../../types/chartPaneSeparator').ChartPaneSeparatorOptions;
|
chartPaneSeparator?: import('../../types/chartPaneSeparator').ChartPaneSeparatorOptions;
|
||||||
/** 전체보기 분할 pane — 캔버스 높이 100% */
|
/** 전체보기 분할 pane — 캔버스 높이 100% */
|
||||||
@@ -65,10 +65,11 @@ const VirtualTargetCardChart: React.FC<Props> = ({
|
|||||||
setTimeframe(resolveStrategyPrimaryTimeframe(strategy));
|
setTimeframe(resolveStrategyPrimaryTimeframe(strategy));
|
||||||
}, [strategy, market, chartTimeframe]);
|
}, [strategy, market, chartTimeframe]);
|
||||||
|
|
||||||
const indicators = useMemo(
|
const indicators = useMemo(() => (
|
||||||
() => buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig),
|
fillHeight
|
||||||
[strategy, getParams, getVisualConfig],
|
? buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig)
|
||||||
);
|
: buildVirtualTradingCardChartIndicators(strategy, getParams, getVisualConfig)
|
||||||
|
), [strategy, getParams, getVisualConfig, fillHeight]);
|
||||||
|
|
||||||
const effectiveIndicators = useMemo(
|
const effectiveIndicators = useMemo(
|
||||||
() => indicators.filter(ind => ind.timeframeVisibility?.[timeframe] !== false),
|
() => indicators.filter(ind => ind.timeframeVisibility?.[timeframe] !== false),
|
||||||
@@ -92,6 +93,10 @@ const VirtualTargetCardChart: React.FC<Props> = ({
|
|||||||
if (!pending || !mgr || barsMarketRef.current !== marketRef.current) return;
|
if (!pending || !mgr || barsMarketRef.current !== marketRef.current) return;
|
||||||
pendingBarRef.current = null;
|
pendingBarRef.current = null;
|
||||||
mgr.updateBar(pending);
|
mgr.updateBar(pending);
|
||||||
|
const wrap = canvasWrapRef.current;
|
||||||
|
if (wrap && wrap.clientHeight > 0) {
|
||||||
|
mgr.resetPaneHeights(wrap.clientHeight);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const applyRealtimeBar = useCallback((bar: OHLCVBar, append: boolean) => {
|
const applyRealtimeBar = useCallback((bar: OHLCVBar, append: boolean) => {
|
||||||
@@ -134,6 +139,20 @@ const VirtualTargetCardChart: React.FC<Props> = ({
|
|||||||
|
|
||||||
barsMarketRef.current = barsMarket;
|
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(() => {
|
useLayoutEffect(() => {
|
||||||
chartLiveReadyRef.current = false;
|
chartLiveReadyRef.current = false;
|
||||||
pendingBarRef.current = null;
|
pendingBarRef.current = null;
|
||||||
@@ -141,23 +160,41 @@ const VirtualTargetCardChart: React.FC<Props> = ({
|
|||||||
}, [market, timeframe]);
|
}, [market, timeframe]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timers = [100, 200, 500, 1000, 2000].map(delay =>
|
|
||||||
setTimeout(() => {
|
|
||||||
const wrap = canvasWrapRef.current;
|
const wrap = canvasWrapRef.current;
|
||||||
const mgr = managerRef.current;
|
|
||||||
if (!wrap) return;
|
if (!wrap) return;
|
||||||
const h = wrap.clientHeight;
|
|
||||||
|
const timers = [100, 300, 800, 1500, 2500].map(delay =>
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!canvasWrapRef.current) return;
|
||||||
|
const h = canvasWrapRef.current.clientHeight;
|
||||||
if (h <= 0) return;
|
if (h <= 0) return;
|
||||||
if (mgr?.hasMainSeries()) {
|
if (managerRef.current?.hasMainSeries()) {
|
||||||
mgr.resetPaneHeights(h);
|
managerRef.current.resetPaneHeights(h);
|
||||||
} else if (!chartReloadTriggeredRef.current && delay >= 1000 && bars.length > 0) {
|
} else if (!chartReloadTriggeredRef.current && delay >= 800 && barsReady) {
|
||||||
chartReloadTriggeredRef.current = true;
|
requestChartReload();
|
||||||
setChartReloadTick(t => t + 1);
|
|
||||||
}
|
}
|
||||||
}, delay),
|
}, 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(() => {
|
useEffect(() => {
|
||||||
if (!useUpbit) return;
|
if (!useUpbit) return;
|
||||||
@@ -195,8 +232,12 @@ const VirtualTargetCardChart: React.FC<Props> = ({
|
|||||||
<span>과거 데이터 로딩…</span>
|
<span>과거 데이터 로딩…</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{!isLoading && !barsReady && (
|
||||||
|
<div className="vtd-card-chart-loading">캔들 데이터 수집 중…</div>
|
||||||
|
)}
|
||||||
<TradingChart
|
<TradingChart
|
||||||
key={`${market}-${timeframe}-${chartReloadTick}`}
|
key={`${market}-${timeframe}-${chartReloadTick}`}
|
||||||
|
chartVisible
|
||||||
bars={bars}
|
bars={bars}
|
||||||
barsMarket={barsMarket}
|
barsMarket={barsMarket}
|
||||||
market={market}
|
market={market}
|
||||||
@@ -210,7 +251,6 @@ const VirtualTargetCardChart: React.FC<Props> = ({
|
|||||||
logScale={false}
|
logScale={false}
|
||||||
drawingsLocked
|
drawingsLocked
|
||||||
drawingsVisible={false}
|
drawingsVisible={false}
|
||||||
displayTimezone={DEFAULT_DISPLAY_TIMEZONE}
|
|
||||||
onCrosshair={noop as (d: LegendData | null) => void}
|
onCrosshair={noop as (d: LegendData | null) => void}
|
||||||
onManagerReady={mgr => { managerRef.current = mgr; }}
|
onManagerReady={mgr => { managerRef.current = mgr; }}
|
||||||
onAddDrawing={noop as (d: Drawing) => void}
|
onAddDrawing={noop as (d: Drawing) => void}
|
||||||
|
|||||||
@@ -1313,8 +1313,19 @@
|
|||||||
color: var(--accent, #3f7ef5);
|
color: var(--accent, #3f7ef5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vtd-card--chart-mode {
|
.vtd-card.vtd-card--chart-mode {
|
||||||
min-height: 420px;
|
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 {
|
.vtd-card-chart-panel {
|
||||||
|
|||||||
@@ -266,3 +266,12 @@ export function buildVirtualTradingChartIndicators(
|
|||||||
if (!defaultIchimoku) return strategyIndicators;
|
if (!defaultIchimoku) return strategyIndicators;
|
||||||
return [defaultIchimoku, ...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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user