obv 문제
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ export function calcOBVWithMA(
|
||||
obv.push(0);
|
||||
continue;
|
||||
}
|
||||
const vol = bars[i].volume;
|
||||
const vol = Number(bars[i].volume) || 0;
|
||||
if (bars[i].close > bars[i - 1].close) acc += vol;
|
||||
else if (bars[i].close < bars[i - 1].close) acc -= vol;
|
||||
obv.push(acc);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
normalizeObvParams,
|
||||
type HLineDef,
|
||||
} from './indicatorRegistry';
|
||||
import { initializeIndicatorConfigForEditor } from './indicatorSettingsEditor';
|
||||
import { buildMainIndicatorConfig } from './indicatorMainConfig';
|
||||
import { normalizeSmaConfig, smaPeriodKey } from './smaConfig';
|
||||
import { normalizeIchimokuConfig } from './ichimokuConfig';
|
||||
@@ -227,7 +228,11 @@ function buildOneIndicator(
|
||||
cfg = { ...cfg, hlines: applyTargetHlines(cfg.hlines ?? [], ref.targetValue) };
|
||||
}
|
||||
|
||||
return enrichIndicatorConfig(cfg);
|
||||
return initializeIndicatorConfigForEditor(
|
||||
cfg,
|
||||
getParams,
|
||||
(type, defaultPlots, defaultHlines) => getVisual(type, defaultPlots, defaultHlines ?? []),
|
||||
);
|
||||
}
|
||||
|
||||
/** 전략에 포함된 보조지표를 차트용 IndicatorConfig 로 변환 (중복 type 제거) */
|
||||
|
||||
Reference in New Issue
Block a user