전략편집기 복합지표 기능 반영
This commit is contained in:
@@ -154,6 +154,8 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
const prevChartType = useRef<ChartType>(chartType);
|
||||
const prevTheme = useRef<Theme>(theme);
|
||||
const prevLogScale = useRef<boolean>(logScale);
|
||||
const indicatorsRef = useRef(indicators);
|
||||
const recoveryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const [chartMgr, setChartMgr] = useState<ChartManager | null>(null);
|
||||
/** 캔들 pane 전체보기 (오버레이 지표 유지, 하단 보조지표 pane 숨김) */
|
||||
@@ -163,6 +165,10 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
barsRef.current = bars;
|
||||
}, [bars]);
|
||||
|
||||
useEffect(() => {
|
||||
indicatorsRef.current = indicators;
|
||||
}, [indicators]);
|
||||
|
||||
const toggleCandleOnly = useCallback(() => {
|
||||
setCandleOnlyMode(v => !v);
|
||||
}, []);
|
||||
@@ -259,6 +265,49 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
const reloadSafetyTimers = useRef<ReturnType<typeof setTimeout>[]>([]);
|
||||
const reloadGenRef = useRef(0);
|
||||
const reloadRetryRef = useRef(0);
|
||||
const reloadAllRef = useRef<(
|
||||
mgr: ChartManager,
|
||||
newBars: OHLCVBar[],
|
||||
ct: ChartType,
|
||||
th: Theme,
|
||||
ls: boolean,
|
||||
inds: IndicatorConfig[],
|
||||
) => Promise<void>>(async () => {});
|
||||
|
||||
const queueIndicatorRecovery = useCallback(() => {
|
||||
if (recoveryTimerRef.current) clearTimeout(recoveryTimerRef.current);
|
||||
recoveryTimerRef.current = window.setTimeout(() => {
|
||||
recoveryTimerRef.current = null;
|
||||
const m = managerRef.current;
|
||||
const barData = barsRef.current;
|
||||
const inds = indicatorsRef.current;
|
||||
if (!m || barData.length === 0) return;
|
||||
if (barsMarket !== undefined && barsMarket !== market) return;
|
||||
|
||||
const expected = countExpectedVisibleIndicators(inds);
|
||||
if (expected === 0) return;
|
||||
if (m.getLoadedIndicatorCount() >= expected) return;
|
||||
|
||||
prevBarsKey.current = '';
|
||||
const ct = prevChartType.current;
|
||||
const th = prevTheme.current;
|
||||
const ls = prevLogScale.current;
|
||||
|
||||
if (m.hasMainSeries()) {
|
||||
void m.reloadIndicatorsOnly(inds).then(() => {
|
||||
if (m.getLoadedIndicatorCount() >= expected) {
|
||||
prevBarsKey.current = barsKey(barData, market);
|
||||
prevIndKey.current = indKey(inds);
|
||||
applyPaneLayout(m);
|
||||
return;
|
||||
}
|
||||
reloadAllRef.current?.(m, barData, ct, th, ls, inds);
|
||||
});
|
||||
return;
|
||||
}
|
||||
reloadAllRef.current?.(m, barData, ct, th, ls, inds);
|
||||
}, 120);
|
||||
}, [market, barsMarket, applyPaneLayout]);
|
||||
|
||||
const reloadAll = useCallback(async (
|
||||
mgr: ChartManager,
|
||||
@@ -280,6 +329,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
mgr.setData(newBars, ct, th);
|
||||
if (gen !== reloadGenRef.current) {
|
||||
prevBarsKey.current = '';
|
||||
queueIndicatorRecovery();
|
||||
return;
|
||||
}
|
||||
mgr.setTheme(th);
|
||||
@@ -289,6 +339,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
for (const ind of inds) {
|
||||
if (gen !== reloadGenRef.current) {
|
||||
prevBarsKey.current = '';
|
||||
queueIndicatorRecovery();
|
||||
return;
|
||||
}
|
||||
await mgr.addIndicator(ind);
|
||||
@@ -296,21 +347,30 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
|
||||
if (gen !== reloadGenRef.current) {
|
||||
prevBarsKey.current = '';
|
||||
queueIndicatorRecovery();
|
||||
return;
|
||||
}
|
||||
|
||||
const expectIndicators = inds.some(i => i.hidden !== true);
|
||||
if (expectIndicators && mgr.getIndicatorCount() === 0) {
|
||||
const expected = countExpectedVisibleIndicators(inds);
|
||||
const loaded = mgr.getLoadedIndicatorCount();
|
||||
|
||||
if (expected > 0 && loaded < expected) {
|
||||
prevBarsKey.current = '';
|
||||
if (reloadRetryRef.current < 3) {
|
||||
if (reloadRetryRef.current < 4 && mgr.hasMainSeries()) {
|
||||
reloadRetryRef.current += 1;
|
||||
requestAnimationFrame(() => {
|
||||
if (managerRef.current) {
|
||||
void reloadAll(managerRef.current, newBars, ct, th, ls, inds);
|
||||
}
|
||||
});
|
||||
await mgr.reloadIndicatorsOnly(inds);
|
||||
if (gen !== reloadGenRef.current) {
|
||||
queueIndicatorRecovery();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mgr.getLoadedIndicatorCount() < expected) {
|
||||
if (reloadRetryRef.current < 6) {
|
||||
reloadRetryRef.current += 1;
|
||||
queueIndicatorRecovery();
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
reloadRetryRef.current = 0;
|
||||
@@ -332,6 +392,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
|
||||
if (gen !== reloadGenRef.current) {
|
||||
prevBarsKey.current = '';
|
||||
queueIndicatorRecovery();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -341,6 +402,12 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
setTimeout(() => {
|
||||
const m = managerRef.current;
|
||||
if (!m || !m.hasMainSeries()) return;
|
||||
const expectedLater = countExpectedVisibleIndicators(indicatorsRef.current);
|
||||
if (expectedLater > 0 && m.getLoadedIndicatorCount() < expectedLater) {
|
||||
prevBarsKey.current = '';
|
||||
queueIndicatorRecovery();
|
||||
return;
|
||||
}
|
||||
const w = wrapperRef.current;
|
||||
if (!w || w.clientHeight <= 0) return;
|
||||
m.resetPaneHeights(w.clientHeight);
|
||||
@@ -349,7 +416,11 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
if (delay === 1400) onDataLoaded?.();
|
||||
}, delay)
|
||||
);
|
||||
}, [applyPaneLayout, onDataLoaded, displayTimezone, timeframe, market]);
|
||||
}, [applyPaneLayout, onDataLoaded, displayTimezone, timeframe, market, queueIndicatorRecovery]);
|
||||
|
||||
useEffect(() => {
|
||||
reloadAllRef.current = reloadAll;
|
||||
}, [reloadAll]);
|
||||
|
||||
// ── 차트 초기화 (마운트 시 1회) ──────────────────────────────────────────
|
||||
useEffect(() => {
|
||||
@@ -633,6 +704,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
}
|
||||
reloadSafetyTimers.current.forEach(clearTimeout);
|
||||
reloadSafetyTimers.current = [];
|
||||
if (recoveryTimerRef.current) clearTimeout(recoveryTimerRef.current);
|
||||
ro.disconnect();
|
||||
managerRef.current?.destroy();
|
||||
managerRef.current = null;
|
||||
@@ -682,10 +754,11 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
const ctChanged = chartType !== prevChartType.current;
|
||||
const thChanged = theme !== prevTheme.current;
|
||||
const lsChanged = logScale !== prevLogScale.current;
|
||||
const expectIndicators = indicators.some(i => i.hidden !== true);
|
||||
const indicatorsIncomplete = expectIndicators
|
||||
const expectIndicators = countExpectedVisibleIndicators(indicators);
|
||||
const loadedIndicators = mgr.getLoadedIndicatorCount();
|
||||
const indicatorsIncomplete = expectIndicators > 0
|
||||
&& mgr.hasMainSeries()
|
||||
&& mgr.getIndicatorCount() === 0;
|
||||
&& loadedIndicators < expectIndicators;
|
||||
|
||||
if (!barsChanged && !indChanged && !ctChanged && !thChanged && !lsChanged && !indicatorsIncomplete) return;
|
||||
|
||||
@@ -778,18 +851,24 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
if (!mgr || !chartMgr || bars.length === 0) return;
|
||||
if (barsMarket !== undefined && barsMarket !== market) return;
|
||||
|
||||
const expectIndicators = indicators.some(i => i.hidden !== true);
|
||||
if (!expectIndicators) return;
|
||||
const expected = countExpectedVisibleIndicators(indicators);
|
||||
if (expected === 0) return;
|
||||
if (mgr.getLoadedIndicatorCount() >= expected) return;
|
||||
|
||||
const t = window.setTimeout(() => {
|
||||
const m = managerRef.current;
|
||||
if (!m || !m.hasMainSeries() || m.getIndicatorCount() > 0) return;
|
||||
prevBarsKey.current = '';
|
||||
void reloadAll(m, bars, chartType, theme, logScale, indicators);
|
||||
}, 120);
|
||||
const timers = [120, 400, 900].map(delay =>
|
||||
window.setTimeout(() => {
|
||||
const m = managerRef.current;
|
||||
if (!m || !m.hasMainSeries()) return;
|
||||
if (barsMarket !== undefined && barsMarket !== market) return;
|
||||
const exp = countExpectedVisibleIndicators(indicatorsRef.current);
|
||||
if (exp === 0 || m.getLoadedIndicatorCount() >= exp) return;
|
||||
prevBarsKey.current = '';
|
||||
queueIndicatorRecovery();
|
||||
}, delay),
|
||||
);
|
||||
|
||||
return () => window.clearTimeout(t);
|
||||
}, [chartMgr, bars, barsMarket, market, chartType, theme, logScale, indicators, reloadAll]);
|
||||
return () => { timers.forEach(clearTimeout); };
|
||||
}, [chartMgr, bars, barsMarket, market, chartType, theme, logScale, indicators, queueIndicatorRecovery]);
|
||||
|
||||
// cursor 모드에서 드로잉 위에 있으면 pointer 커서로 변경
|
||||
const handleWrapperMouseMove = useCallback((e: React.PointerEvent) => {
|
||||
@@ -988,10 +1067,15 @@ const PaneLegendPortal: React.FC<
|
||||
};
|
||||
|
||||
// ── 변경 감지용 키 생성 헬퍼 ────────────────────────────────────────────────
|
||||
function countExpectedVisibleIndicators(inds: IndicatorConfig[]): number {
|
||||
return inds.filter(i => i.hidden !== true).length;
|
||||
}
|
||||
|
||||
function barsKey(bars: OHLCVBar[], market = ''): string {
|
||||
if (bars.length === 0) return '';
|
||||
const last = bars[bars.length - 1];
|
||||
return `${market}:${bars.length}:${bars[0].time}:${last.time}:${last.close}`;
|
||||
// last.close 제외 — 틱마다 full reload 방지 (실시간은 updateBar/appendBar 경로)
|
||||
return `${market}:${bars.length}:${bars[0].time}:${last.time}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user