알림목록 obv 그래프 문제 최종수정
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* 매매 시그널 알림 목록 행 — 4칸이 목록 너비를 꽉 채움, 보조지표 2개↑ 시 우측 슬롯만 스크롤
|
||||
*/
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState, memo } from 'react';
|
||||
import type { Theme } from '../../types';
|
||||
import type { Theme, IndicatorConfig } from '../../types';
|
||||
import type { TickerData } from '../../hooks/useMarketTicker';
|
||||
import { useLiveReceiveFlash } from '../../hooks/useLiveReceiveFlash';
|
||||
import { useTradeNotificationSignalSnapshot } from '../../hooks/useTradeNotificationSignalSnapshot';
|
||||
@@ -23,8 +23,10 @@ import {
|
||||
buildStrategyChartIndicators,
|
||||
candleTypeToTimeframe,
|
||||
} from '../../utils/strategyToChartIndicators';
|
||||
import { buildNotificationChartIndicators } from '../../utils/notificationChartIndicators';
|
||||
import { formatIndicatorDisplayLabel } from '../../utils/indicatorRegistry';
|
||||
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
|
||||
import { ensureWorkspaceChartIndicators, invalidateWorkspaceChartIndicatorsCache } from '../../utils/workspaceChartIndicatorsCache';
|
||||
import TradeSignalChartCard from './TradeSignalChartCard';
|
||||
import TradeNotificationSignalCard from './TradeNotificationSignalCard';
|
||||
import TradeNotificationHScrollPane from './TradeNotificationHScrollPane';
|
||||
@@ -109,7 +111,9 @@ const TradeNotificationListRow: React.FC<Props> = ({
|
||||
const chartTf = candleTypeToTimeframe(item.candleType ?? '1m');
|
||||
const candleKo = formatCandleTypeKo(item.candleType);
|
||||
|
||||
const { getParams, getVisualConfig } = useIndicatorSettings();
|
||||
const { getParams, getVisualConfig, settingsRevision } = useIndicatorSettings();
|
||||
const [wsIndicators, setWsIndicators] = useState<IndicatorConfig[]>([]);
|
||||
const [wsRevision, setWsRevision] = useState(0);
|
||||
const [strategy, setStrategy] = useState<StrategyDto | undefined>();
|
||||
const [strategies, setStrategies] = useState<StrategyDto[]>([]);
|
||||
const strategyLabel = item.strategyName?.trim()
|
||||
@@ -166,16 +170,28 @@ const TradeNotificationListRow: React.FC<Props> = ({
|
||||
setExpandedChartKey(null);
|
||||
}, [item.id, layoutMode]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
invalidateWorkspaceChartIndicatorsCache();
|
||||
void ensureWorkspaceChartIndicators().then(inds => {
|
||||
if (cancelled) return;
|
||||
setWsIndicators(inds);
|
||||
setWsRevision(r => r + 1);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [settingsRevision]);
|
||||
|
||||
const indicatorCards = useMemo(() => {
|
||||
if (!isListLayout || !strategy) return [];
|
||||
const all = buildStrategyChartIndicators(strategy, getParams, getVisualConfig);
|
||||
const strategyBuilt = buildStrategyChartIndicators(strategy, getParams, getVisualConfig, wsIndicators);
|
||||
const all = buildNotificationChartIndicators(strategyBuilt, wsIndicators);
|
||||
return all.slice(0, MAX_INDICATOR_CARDS).map(ind => ({
|
||||
id: ind.id,
|
||||
type: ind.type,
|
||||
label: formatIndicatorDisplayLabel(ind.type),
|
||||
config: [ind],
|
||||
}));
|
||||
}, [strategy, getParams, getVisualConfig, isListLayout]);
|
||||
}, [strategy, getParams, getVisualConfig, settingsRevision, wsRevision, wsIndicators, isListLayout]);
|
||||
|
||||
/** 통합 차트용: 전략의 모든 보조지표를 하나의 차트에 넘긴다 */
|
||||
const allIndicatorConfigs = useMemo(
|
||||
|
||||
@@ -17,13 +17,13 @@ import { useAppSettings } from '../../hooks/useAppSettings';
|
||||
import type { ChartManager } from '../../utils/ChartManager';
|
||||
import { pinCandleWatch } from '../../utils/backendApi';
|
||||
import { timeframeToCandleType } from '../../utils/chartCandleType';
|
||||
import { DEFAULT_DISPLAY_TIMEZONE } from '../../utils/timezone';
|
||||
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
|
||||
import { useSessionCandleOverlayControls } from '../../hooks/useSessionCandleOverlayControls';
|
||||
import { ensurePaperChartOverlays } from '../../utils/strategyToChartIndicators';
|
||||
import { mergeGlobalDefaultsOntoChartIndicators } from '../../utils/indicatorMainConfig';
|
||||
import { barsMeetObvVolumeRequirement } from '../../utils/obvChartBars';
|
||||
import { enrichIndicatorConfig } from '../../utils/indicatorRegistry';
|
||||
import { buildNotificationChartIndicators } from '../../utils/notificationChartIndicators';
|
||||
import { ensurePaperChartOverlays } from '../../utils/strategyToChartIndicators';
|
||||
import { ensureWorkspaceChartIndicators, invalidateWorkspaceChartIndicatorsCache } from '../../utils/workspaceChartIndicatorsCache';
|
||||
import { indicatorDefaultsFingerprint } from '../../utils/indicatorDefaultsFingerprint';
|
||||
import {
|
||||
applyNotificationSignalMarkers,
|
||||
type TradeSignalChartMarker,
|
||||
@@ -70,21 +70,42 @@ const TradeSignalMiniChart: React.FC<Props> = ({
|
||||
const signalMarkerRef = useRef(signalMarker);
|
||||
signalMarkerRef.current = signalMarker;
|
||||
|
||||
const baseIndicators = useMemo(
|
||||
() => mergeGlobalDefaultsOntoChartIndicators(
|
||||
const [wsIndicators, setWsIndicators] = useState<IndicatorConfig[]>([]);
|
||||
const [wsRevision, setWsRevision] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
invalidateWorkspaceChartIndicatorsCache();
|
||||
void ensureWorkspaceChartIndicators().then(inds => {
|
||||
if (cancelled) return;
|
||||
setWsIndicators(inds);
|
||||
setWsRevision(r => r + 1);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [settingsRevision]);
|
||||
|
||||
const baseIndicators = useMemo(() => {
|
||||
const strategyMerged = mergeGlobalDefaultsOntoChartIndicators(
|
||||
ensurePaperChartOverlays(indicators, getParams, getVisualConfig),
|
||||
getParams,
|
||||
getVisualConfig,
|
||||
).map(enrichIndicatorConfig),
|
||||
[indicators, getParams, getVisualConfig, settingsRevision],
|
||||
wsIndicators,
|
||||
);
|
||||
return buildNotificationChartIndicators(strategyMerged, wsIndicators);
|
||||
}, [indicators, getParams, getVisualConfig, settingsRevision, wsRevision, wsIndicators]);
|
||||
|
||||
const indicatorStyleKey = useMemo(
|
||||
() => baseIndicators.map(i => indicatorDefaultsFingerprint(i)).join('|'),
|
||||
[baseIndicators],
|
||||
);
|
||||
|
||||
const needsDeepObv = useMemo(
|
||||
/** OBV 등 워크스페이스 지표가 있으면 ws config 로드 완료 후 렌더 (maLength=1 플래시 방지) */
|
||||
const needsWorkspaceConfig = useMemo(
|
||||
() => baseIndicators.some(i => i.type === 'OBV'),
|
||||
[baseIndicators],
|
||||
);
|
||||
|
||||
/** 메인 차트와 동일 소스 — STOMP(+ volume 없으면 Upbit 폴백). deepObvHistory 로 장기 봉 확보 */
|
||||
/** 실시간(메인) 차트와 동일한 데이터 소스 사용 */
|
||||
const chartRealtimeSource: ChartRealtimeSource = appChartSource;
|
||||
|
||||
const {
|
||||
@@ -162,28 +183,23 @@ const TradeSignalMiniChart: React.FC<Props> = ({
|
||||
onNewCandle: handleNewCandle,
|
||||
enabled: enabled && useUpbit,
|
||||
source: chartRealtimeSource,
|
||||
deepObvHistory: needsDeepObv,
|
||||
}), [handleTickUpdate, handleNewCandle, enabled, useUpbit, needsDeepObv, chartRealtimeSource]),
|
||||
}), [handleTickUpdate, handleNewCandle, enabled, useUpbit, chartRealtimeSource]),
|
||||
);
|
||||
|
||||
const { isLoadingMore } = useHistoryLoader({
|
||||
symbol: market,
|
||||
timeframe,
|
||||
/** OBV — 백엔드 history volume=0 봉 prepend 시 본선 왜곡 방지 */
|
||||
isUpbit: useUpbit && enabled && !needsDeepObv,
|
||||
isUpbit: useUpbit && enabled,
|
||||
managerRef,
|
||||
});
|
||||
|
||||
barsMarketRef.current = barsMarket;
|
||||
const barsReady = bars.length >= 2 && barsMarket === market;
|
||||
|
||||
const obvBarsReady = useMemo(() => {
|
||||
if (!needsDeepObv) return true;
|
||||
if (bars.length < 2) return false;
|
||||
return barsMeetObvVolumeRequirement(bars);
|
||||
}, [needsDeepObv, bars]);
|
||||
// 워크스페이스 지표(OBV maLength 등) 로드 완료 전에는 렌더 보류 → 잘못된 기본값 플래시 방지
|
||||
const wsConfigReady = !needsWorkspaceConfig || wsRevision > 0;
|
||||
|
||||
const chartDataReady = barsReady && obvBarsReady && settingsLoaded;
|
||||
const chartDataReady = barsReady && wsConfigReady && settingsLoaded;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
chartLiveReadyRef.current = false;
|
||||
@@ -265,9 +281,6 @@ const TradeSignalMiniChart: React.FC<Props> = ({
|
||||
].filter(Boolean).join(' ')}
|
||||
>
|
||||
{(!settingsLoaded || isLoading) && <div className="tnl-mini-chart-loading">로딩…</div>}
|
||||
{needsDeepObv && !isLoading && !obvBarsReady && (
|
||||
<div className="tnl-mini-chart-loading">OBV 거래량 데이터 로딩…</div>
|
||||
)}
|
||||
{isLoadingMore && (
|
||||
<div className="chart-history-loading">
|
||||
<div className="loading-spinner" style={{ width: 12, height: 12 }} />
|
||||
@@ -275,7 +288,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
|
||||
)}
|
||||
{chartDataReady && (
|
||||
<TradingChart
|
||||
key={`${market}-${timeframe}-${chartReloadTick}-${chartRealtimeSource}-${needsDeepObv ? 'obv' : 'std'}-${settingsRevision}-${chartIndicators.map(i => i.id).join(',')}`}
|
||||
key={`${market}-${timeframe}-${chartReloadTick}-${chartRealtimeSource}-${settingsRevision}-${wsRevision}-${indicatorStyleKey}-${chartIndicators.map(i => i.id).join(',')}`}
|
||||
chartVisible
|
||||
bars={bars}
|
||||
barsMarket={barsMarket}
|
||||
|
||||
Reference in New Issue
Block a user