obv 추가수정
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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,20 +1097,26 @@ 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);
|
const config = enrichIndicatorConfig(entry.config);
|
||||||
if (entry.seriesList.length === 0 || this.rawBars.length === 0) continue;
|
const def = getIndicatorDef(entry.type);
|
||||||
try {
|
if (!def) continue;
|
||||||
const config = enrichIndicatorConfig(entry.config);
|
|
||||||
const result = await calculateIndicator(
|
const missing = this._dualLineMissingPlotIds(entry, config, def);
|
||||||
config.type, this.rawBars.slice(), config.params ?? {},
|
if (missing.length > 0) {
|
||||||
);
|
await this._rebuildDualLinePlotSeriesForEntry(entry, config, def);
|
||||||
this._setIndicatorPlotsFullData(entry, result.plots as Record<string, PlotData>, config);
|
} else if (entry.seriesList.length > 0 && this.rawBars.length > 0) {
|
||||||
this.applyIndicatorStyle(config);
|
try {
|
||||||
} catch { /* ok */ }
|
const result = await calculateIndicator(
|
||||||
|
config.type, this.rawBars.slice(), config.params ?? {},
|
||||||
|
);
|
||||||
|
this._setIndicatorPlotsFullData(entry, result.plots as Record<string, PlotData>, config);
|
||||||
|
} catch { /* ok */ }
|
||||||
|
}
|
||||||
|
this.applyIndicatorStyle(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user