obv 추가수정

This commit is contained in:
Macbook
2026-06-09 22:18:18 +09:00
parent 27c60be1e9
commit 7b4fc1f811
5 changed files with 79 additions and 28 deletions
+1
View File
@@ -1446,6 +1446,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
for (const ind of indicators) { for (const ind of indicators) {
mgr.applyIndicatorStyle(ind); mgr.applyIndicatorStyle(ind);
} }
void mgr.repairDualLineSeries();
syncIndicatorTrackingRefs(indicators); syncIndicatorTrackingRefs(indicators);
} else if ( } else if (
removedIds.length > 0 removedIds.length > 0
@@ -54,7 +54,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
onRealtimeActivity, onRealtimeActivity,
signalMarker, signalMarker,
}) => { }) => {
const { getParams, getVisualConfig } = useIndicatorSettings(); const { getParams, getVisualConfig, settingsLoaded, settingsRevision } = useIndicatorSettings();
const { defaults: appDefaults } = useAppSettings(); const { defaults: appDefaults } = useAppSettings();
const appChartSource = (appDefaults.chartRealtimeSource ?? 'BACKEND_STOMP') as ChartRealtimeSource; const appChartSource = (appDefaults.chartRealtimeSource ?? 'BACKEND_STOMP') as ChartRealtimeSource;
const managerRef = useRef<ChartManager | null>(null); const managerRef = useRef<ChartManager | null>(null);
@@ -75,7 +75,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
getParams, getParams,
getVisualConfig, getVisualConfig,
).map(enrichIndicatorConfig), ).map(enrichIndicatorConfig),
[indicators, getParams, getVisualConfig], [indicators, getParams, getVisualConfig, settingsRevision],
); );
const needsDeepObv = useMemo( const needsDeepObv = useMemo(
@@ -188,7 +188,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
return bars.some(b => (Number(b.volume) || 0) > 0); return bars.some(b => (Number(b.volume) || 0) > 0);
}, [needsDeepObv, bars]); }, [needsDeepObv, bars]);
const chartDataReady = barsReady && obvBarsReady; const chartDataReady = barsReady && obvBarsReady && settingsLoaded;
useEffect(() => { useEffect(() => {
if (!enabled || !chartDataReady) return; if (!enabled || !chartDataReady) return;
@@ -198,7 +198,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
setTimeout(() => void mgr.repairDualLineSeries(), delay), setTimeout(() => void mgr.repairDualLineSeries(), delay),
); );
return () => timers.forEach(clearTimeout); return () => timers.forEach(clearTimeout);
}, [enabled, chartDataReady, chartIndicators, market, timeframe]); }, [enabled, chartDataReady, chartIndicators, market, timeframe, settingsRevision]);
useLayoutEffect(() => { useLayoutEffect(() => {
chartLiveReadyRef.current = false; chartLiveReadyRef.current = false;
@@ -279,7 +279,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
fillHeight ? 'tnl-mini-chart-canvas--fill' : '', fillHeight ? 'tnl-mini-chart-canvas--fill' : '',
].filter(Boolean).join(' ')} ].filter(Boolean).join(' ')}
> >
{isLoading && <div className="tnl-mini-chart-loading"></div>} {(!settingsLoaded || isLoading) && <div className="tnl-mini-chart-loading"></div>}
{needsDeepObv && !isLoading && !obvBarsReady && ( {needsDeepObv && !isLoading && !obvBarsReady && (
<div className="tnl-mini-chart-loading">OBV </div> <div className="tnl-mini-chart-loading">OBV </div>
)} )}
@@ -290,7 +290,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
)} )}
{chartDataReady && ( {chartDataReady && (
<TradingChart <TradingChart
key={`${market}-${timeframe}-${chartReloadTick}-${chartRealtimeSource}-${needsDeepObv ? 'obv' : 'std'}-${chartIndicators.map(i => i.id).join(',')}`} key={`${market}-${timeframe}-${chartReloadTick}-${chartRealtimeSource}-${needsDeepObv ? 'obv' : 'std'}-${settingsRevision}-${chartIndicators.map(i => i.id).join(',')}`}
chartVisible chartVisible
bars={bars} bars={bars}
barsMarket={barsMarket} barsMarket={barsMarket}
+59 -15
View File
@@ -808,17 +808,27 @@ export class ChartManager {
} }
} }
/** 스타일 갱신·초기 추가 후 OBV 등 본선(plot0) 시리즈 누락 보정 + 전체 setData */ /** plot0·plot1 시리즈 전부 제거 후 DB 설정 스타일로 재생성 */
private async _ensureDualLinePlotSeriesForEntry(entry: IndicatorEntry): Promise<void> { private async _rebuildDualLinePlotSeriesForEntry(
if (!DUAL_LINE_INDICATOR_TYPES.has(entry.type)) return; entry: IndicatorEntry,
const config = enrichIndicatorConfig(entry.config!); config: IndicatorConfig,
const def = getIndicatorDef(config.type); def: NonNullable<ReturnType<typeof getIndicatorDef>>,
if (!def) return; ): Promise<void> {
if (this._dualLineMissingPlotIds(entry, config, def).length === 0) return;
if (this.rawBars.length === 0) return; if (this.rawBars.length === 0) return;
const pane = entry.paneIndex ?? this._readSeriesPaneIndex(entry.seriesList[0]) ?? 2; const pane = entry.paneIndex ?? this._readSeriesPaneIndex(entry.seriesList[0]) ?? 2;
const indicatorHidden = !this._isIndicatorEffectivelyVisible(config); const indicatorHidden = !this._isIndicatorEffectivelyVisible(config);
for (const pl of entry.hlineRefs) {
try { entry.seriesList[0]?.removePriceLine(pl); } catch { /* ok */ }
}
entry.hlineRefs = [];
for (const s of entry.seriesList) {
try { this.chart.removeSeries(s); } catch { /* ok */ }
}
entry.seriesList = [];
entry.seriesMeta = [];
try { try {
const result = await calculateIndicator(config.type, this.rawBars.slice(), config.params); const result = await calculateIndicator(config.type, this.rawBars.slice(), config.params);
this._appendMissingPlotSeriesToLists( this._appendMissingPlotSeriesToLists(
@@ -838,6 +848,30 @@ export class ChartManager {
const actualPane = this._readSeriesPaneIndex(entry.seriesList[0]); const actualPane = this._readSeriesPaneIndex(entry.seriesList[0]);
if (actualPane >= 2) entry.paneIndex = actualPane; if (actualPane >= 2) entry.paneIndex = actualPane;
} }
} catch (e) {
console.error(`[Indicator] ${config.type} dual-line rebuild error:`, e);
}
}
/** 스타일 갱신·초기 추가 후 OBV 등 본선(plot0) 시리즈 누락 보정 + 전체 setData */
private async _ensureDualLinePlotSeriesForEntry(entry: IndicatorEntry): Promise<void> {
if (!DUAL_LINE_INDICATOR_TYPES.has(entry.type)) return;
const config = enrichIndicatorConfig(entry.config!);
const def = getIndicatorDef(config.type);
if (!def) return;
if (this.rawBars.length === 0) return;
const missing = this._dualLineMissingPlotIds(entry, config, def);
if (missing.length > 0) {
await this._rebuildDualLinePlotSeriesForEntry(entry, config, def);
return;
}
if (entry.seriesList.length === 0) return;
try {
const result = await calculateIndicator(config.type, this.rawBars.slice(), config.params);
this._setIndicatorPlotsFullData(entry, result.plots as Record<string, PlotData>, config);
entry.config = config;
} catch (e) { } catch (e) {
console.error(`[Indicator] ${config.type} dual-line repair error:`, e); console.error(`[Indicator] ${config.type} dual-line repair error:`, e);
} }
@@ -1063,21 +1097,27 @@ export class ChartManager {
this._notifyPaneLayout(); this._notifyPaneLayout();
} }
/** OBV·RSI 등 본선(plot0) 시리즈 누락 시 재생성 + 전체 plot setData */ /** OBV·RSI 등 본선(plot0) 시리즈 누락 시 재생성 + 전체 plot setData + 스타일(DB) 반영 */
async repairDualLineSeries(): Promise<void> { async repairDualLineSeries(): Promise<void> {
for (const entry of this.indicators.values()) { for (const entry of this.indicators.values()) {
if (!DUAL_LINE_INDICATOR_TYPES.has(entry.type) || !entry.config) continue; if (!DUAL_LINE_INDICATOR_TYPES.has(entry.type) || !entry.config) continue;
await this._ensureDualLinePlotSeriesForEntry(entry);
if (entry.seriesList.length === 0 || this.rawBars.length === 0) continue;
try {
const config = enrichIndicatorConfig(entry.config); const config = enrichIndicatorConfig(entry.config);
const def = getIndicatorDef(entry.type);
if (!def) continue;
const missing = this._dualLineMissingPlotIds(entry, config, def);
if (missing.length > 0) {
await this._rebuildDualLinePlotSeriesForEntry(entry, config, def);
} else if (entry.seriesList.length > 0 && this.rawBars.length > 0) {
try {
const result = await calculateIndicator( const result = await calculateIndicator(
config.type, this.rawBars.slice(), config.params ?? {}, config.type, this.rawBars.slice(), config.params ?? {},
); );
this._setIndicatorPlotsFullData(entry, result.plots as Record<string, PlotData>, config); this._setIndicatorPlotsFullData(entry, result.plots as Record<string, PlotData>, config);
this.applyIndicatorStyle(config);
} catch { /* ok */ } } catch { /* ok */ }
} }
this.applyIndicatorStyle(config);
}
} }
/** 여러 지표 추가 후 pane 레이아웃을 한 번만 갱신 */ /** 여러 지표 추가 후 pane 레이아웃을 한 번만 갱신 */
@@ -2902,7 +2942,7 @@ export class ChartManager {
* 인디케이터의 스타일(색상·선폭·plotVisibility)을 in-place 로 일괄 적용. * 인디케이터의 스타일(색상·선폭·plotVisibility)을 in-place 로 일괄 적용.
* 시리즈를 제거·재생성하지 않으므로 pane 재번호 문제가 없음. * 시리즈를 제거·재생성하지 않으므로 pane 재번호 문제가 없음.
*/ */
applyIndicatorStyle(config: IndicatorConfig): void { applyIndicatorStyle(config: IndicatorConfig, options?: { skipDualLineRefresh?: boolean }): void {
config = enrichIndicatorConfig(config); config = enrichIndicatorConfig(config);
const entry = this.indicators.get(config.id); const entry = this.indicators.get(config.id);
if (!entry) return; if (!entry) return;
@@ -3035,8 +3075,12 @@ export class ChartManager {
void this._repaintHistogramSeries(config.id, config); void this._repaintHistogramSeries(config.id, config);
} }
if (DUAL_LINE_INDICATOR_TYPES.has(config.type)) { if (DUAL_LINE_INDICATOR_TYPES.has(config.type) && !options?.skipDualLineRefresh) {
void this._ensureDualLinePlotSeriesForEntry(entry); void this._ensureDualLinePlotSeriesForEntry(entry).then(() => {
if (entry.config) {
this.applyIndicatorStyle(enrichIndicatorConfig(entry.config), { skipDualLineRefresh: true });
}
});
} }
if (this._candleOnlyLayout && this._isSubPaneIndicator(entry)) { if (this._candleOnlyLayout && this._isSubPaneIndicator(entry)) {
+1
View File
@@ -453,6 +453,7 @@ function normalizeDualLinePlotDef(saved: PlotDef, reg: PlotDef): PlotDef {
title: reg.title, title: reg.title,
type: 'line', type: 'line',
lineWidth: Math.max(1, saved.lineWidth ?? reg.lineWidth ?? 1), lineWidth: Math.max(1, saved.lineWidth ?? reg.lineWidth ?? 1),
lineStyle: saved.lineStyle ?? reg.lineStyle ?? 'solid',
color: resolvePlotLineColor(saved.color, reg.color), color: resolvePlotLineColor(saved.color, reg.color),
}; };
} }
@@ -64,10 +64,15 @@ export function initializeIndicatorConfigForEditor(
: { ...raw.params }; : { ...raw.params };
const visual = getVisualConfig?.(raw.type, def.plots, def.hlines); const visual = getVisualConfig?.(raw.type, def.plots, def.hlines);
const mergedPlots = mergePlotDefs(
raw.plots?.length ? raw.plots : undefined,
mergePlotDefs(visual?.plots, def.plots),
);
return enrichIndicatorConfig({ return enrichIndicatorConfig({
...raw, ...raw,
params, params,
plots: raw.plots?.length ? raw.plots : visual?.plots, plots: mergedPlots,
hlines: raw.hlines?.length ? raw.hlines : visual?.hlines, hlines: raw.hlines?.length ? raw.hlines : visual?.hlines,
cloudColors: raw.cloudColors ?? visual?.cloudColors, cloudColors: raw.cloudColors ?? visual?.cloudColors,
bandBackground: raw.bandBackground ?? visual?.bandBackground, bandBackground: raw.bandBackground ?? visual?.bandBackground,