obv 문제

This commit is contained in:
Macbook
2026-06-09 01:32:27 +09:00
parent 969b2fa6ad
commit 8670e58c78
4 changed files with 115 additions and 4 deletions
+102
View File
@@ -214,6 +214,9 @@ function detachBbBandFill(entry: IndicatorEntry): void {
entry.bbFillPrimitive = undefined;
}
/** OBV·CCI·RSI·TRIX — 본선+신호선 이중 plot (누락 시리즈 보정 대상) */
const DUAL_LINE_INDICATOR_TYPES = new Set(['OBV', 'CCI', 'RSI', 'TRIX']);
export class ChartManager {
private chart: IChartApi;
private container: HTMLElement;
@@ -722,6 +725,89 @@ export class ChartManager {
return loadGen !== this._dataGeneration;
}
/** 이중 plot 지표 — addIndicator 루프에서 누락된 plot 시리즈 추가 */
private _appendMissingPlotSeriesToLists(
seriesList: ISeriesApi<SeriesType>[],
seriesMeta: IndicatorEntry['seriesMeta'],
config: IndicatorConfig,
result: { plots: Record<string, PlotData> },
pane: number,
indicatorHidden: boolean,
def: ReturnType<typeof getIndicatorDef>,
): void {
const plotDefs = config.plots ?? def?.plots ?? [];
const existingIds = new Set(seriesMeta.map(m => m.plotId));
const indicatorPriceFormat = priceFormatForIndicatorPane(pane);
for (const plotDef of plotDefs) {
if (existingIds.has(plotDef.id)) continue;
if (config.plotVisibility?.[plotDef.id] === false) continue;
if (plotDef.type === 'histogram') continue;
const plotData = result.plots[plotDef.id] as PlotData | undefined;
if (!plotData?.length) continue;
const filtered = plotData.filter(p => p.value !== null && !isNaN(p.value));
if (filtered.length === 0) continue;
const isPlotVisible = !indicatorHidden
&& (this._isCustomOverlayApplied() && (config.type === 'SMA' || config.type === 'BollingerBands' || config.type === 'IchimokuCloud')
? isCustomPlotSelected(this._getAppliedCustomOverlaySelection(), config.type, plotDef.id)
: config.plotVisibility?.[plotDef.id] !== false);
const showPriceLabel = this.shouldShowSeriesPriceLabel(config, true, pane);
const showSeriesTitle = pane < 2 && showPriceLabel;
const regPlot = def?.plots?.find(p => p.id === plotDef.id);
const series = this.chart.addSeries(LineSeries, {
color: resolvePlotLineColor(plotDef.color, regPlot?.color ?? plotDef.color ?? '#2962FF'),
lineWidth: Math.max(1, plotDef.lineWidth ?? 1) as 1 | 2 | 3 | 4,
lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
priceLineVisible: false,
lastValueVisible: showPriceLabel,
title: showSeriesTitle ? this.seriesTitleForPlot(config, plotDef) : '',
visible: isPlotVisible,
...(indicatorPriceFormat ? { priceFormat: indicatorPriceFormat } : {}),
}, pane);
series.setData(filtered.map(p => ({ time: p.time as Time, value: p.value })));
seriesList.push(series);
seriesMeta.push({ plotId: plotDef.id, isHistogram: false, color: plotDef.color });
}
}
/** 스타일 갱신·초기 추가 후 OBV 등 본선(plot0) 시리즈 누락 보정 */
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;
const plotDefs = config.plots ?? def.plots ?? [];
const expectedCount = plotDefs.filter(
p => p.type !== 'histogram' && config.plotVisibility?.[p.id] !== false,
).length;
if (entry.seriesList.length >= expectedCount) return;
if (this.rawBars.length === 0) return;
const pane = entry.paneIndex ?? this._readSeriesPaneIndex(entry.seriesList[0]) ?? 2;
const indicatorHidden = !this._isIndicatorEffectivelyVisible(config);
try {
const result = await calculateIndicator(config.type, this.rawBars.slice(), config.params);
this._appendMissingPlotSeriesToLists(
entry.seriesList,
entry.seriesMeta,
config,
result,
pane,
indicatorHidden,
def,
);
entry.config = config;
if (entry.seriesList.length > 0) {
const actualPane = this._readSeriesPaneIndex(entry.seriesList[0]);
if (actualPane >= 2) entry.paneIndex = actualPane;
}
} catch (e) {
console.error(`[Indicator] ${config.type} dual-line repair error:`, e);
}
}
// ─── Indicators ─────────────────────────────────────────────────────────
async addIndicator(
config: IndicatorConfig,
@@ -807,6 +893,18 @@ export class ChartManager {
seriesList.push(series);
}
if (DUAL_LINE_INDICATOR_TYPES.has(config.type)) {
this._appendMissingPlotSeriesToLists(
seriesList,
seriesMeta,
config,
result,
pane,
indicatorHidden,
def,
);
}
// hlines(과열선/중앙선/침체선)는 첫 번째 시리즈에만 한 번 생성 (중복 방지)
const hlineRefs: IPriceLine[] = [];
let fillPrimitive: IndicatorFillPrimitive | undefined;
@@ -2849,6 +2947,10 @@ export class ChartManager {
void this._repaintHistogramSeries(config.id, config);
}
if (DUAL_LINE_INDICATOR_TYPES.has(config.type)) {
void this._ensureDualLinePlotSeriesForEntry(entry);
}
if (this._candleOnlyLayout && this._isSubPaneIndicator(entry)) {
this._hideSubPaneIndicator(entry);
}