obv 신호선

This commit is contained in:
Macbook
2026-06-09 01:06:35 +09:00
parent 8a2d8054ad
commit 1f0671aa7a
4 changed files with 69 additions and 9 deletions
+1 -1
View File
@@ -791,7 +791,7 @@ export class ChartManager {
const showSeriesTitle = pane < 2 && showPriceLabel;
series = this.chart.addSeries(LineSeries, {
color: plotDef.color,
lineWidth: (plotDef.lineWidth ?? 1) as 1 | 2 | 3 | 4,
lineWidth: Math.max(1, plotDef.lineWidth ?? 1) as 1 | 2 | 3 | 4,
lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
priceLineVisible: false,
lastValueVisible: showPriceLabel,
+51 -7
View File
@@ -441,6 +441,45 @@ export function mergePlotDefs(saved: PlotDef[] | undefined, defaults: PlotDef[])
});
}
/** OBV·CCI·RSI·TRIX — registry 신호선(plot1) 병합·표시 보장 (DB에 plot0만 저장된 경우 대비) */
const SIGNAL_LINE_INDICATOR_TYPES = new Set(['OBV', 'CCI', 'RSI', 'TRIX']);
function normalizeSignalLineIndicatorPlots(
type: string,
plots: PlotDef[],
registryPlots: PlotDef[],
plotVisibility: Record<string, boolean>,
params: Record<string, number | string | boolean>,
): { plots: PlotDef[]; plotVisibility: Record<string, boolean> } {
if (!SIGNAL_LINE_INDICATOR_TYPES.has(type)) {
return { plots, plotVisibility };
}
const merged = mergePlotDefs(plots, registryPlots);
const regSignal = registryPlots.find(p => p.id === 'plot1');
const nextPlots = regSignal
? merged.map(p => {
if (p.id !== 'plot1') return p;
return {
...regSignal,
...p,
id: 'plot1',
title: regSignal.title,
type: 'line' as const,
lineWidth: Math.max(1, p.lineWidth ?? regSignal.lineWidth ?? 1),
};
})
: merged;
const nextVis = { ...plotVisibility };
if (type === 'OBV') {
nextVis.plot1 = true;
} else if ((type === 'CCI' || type === 'RSI') && params.maType === 'None') {
nextVis.plot1 = false;
} else if (nextVis.plot1 !== false) {
nextVis.plot1 = true;
}
return { plots: nextPlots, plotVisibility: nextVis };
}
/** 업비트 OBV 신호선 MA 종류 (단순·지수·가중만) */
export const OBV_MA_TYPE_OPTIONS = ['SMA', 'EMA', 'WMA'] as const;
export type ObvMaType = (typeof OBV_MA_TYPE_OPTIONS)[number];
@@ -520,18 +559,20 @@ export function enrichIndicatorConfig(
if (cfgNorm.type === 'DMI') {
params = normalizeDmiParams(params);
}
const plots = cfgNorm.type === 'BollingerBands'
let plots = cfgNorm.type === 'BollingerBands'
? mergeBbPlots(cfgNorm.plots, def.plots)
: mergePlotDefs(cfgNorm.plots, def.plots);
const plotVisibility: Record<string, boolean> = { ...cfgNorm.plotVisibility };
let plotVisibility: Record<string, boolean> = { ...cfgNorm.plotVisibility };
for (const p of plots) {
if (plotVisibility[p.id] === undefined) plotVisibility[p.id] = true;
}
if (cfgNorm.type === 'OBV') {
plotVisibility.plot1 = plotVisibility.plot1 !== false;
} else if ((cfgNorm.type === 'CCI' || cfgNorm.type === 'RSI') && params.maType === 'None') {
plotVisibility.plot1 = false;
}
({ plots, plotVisibility } = normalizeSignalLineIndicatorPlots(
cfgNorm.type,
plots,
def.plots,
plotVisibility,
params,
));
const out: import('../types').IndicatorConfig = {
...cfgNorm,
@@ -636,6 +677,9 @@ export async function calculateIndicator(
if (type === 'CCI') {
calcParams = normalizeCciParams(params);
}
if (type === 'OBV') {
calcParams = normalizeObvParams(params);
}
if (type === 'IchimokuCloud') {
const { senkou } = resolveIchimokuDisplacements(params);
calcParams = { ...params, displacement: senkou };
@@ -8,6 +8,8 @@ import {
enrichIndicatorConfig,
getIndicatorDef,
getHLineLabel,
mergePlotDefs,
normalizeObvParams,
type HLineDef,
} from './indicatorRegistry';
import { buildMainIndicatorConfig } from './indicatorMainConfig';
@@ -199,6 +201,17 @@ function buildOneIndicator(
plots: mainSma?.plots ?? cfg.plots,
plotVisibility: mainSma?.plotVisibility ?? cfg.plotVisibility,
});
} else if (ref.registryType === 'OBV') {
const p = normalizeObvParams({ ...cfg.params });
const signalLen = ref.rightPeriod ?? ref.period;
if (signalLen != null && signalLen > 0) {
p.maLength = signalLen;
}
cfg = {
...cfg,
params: p,
plots: mergePlotDefs(cfg.plots, def.plots),
};
} else if (ref.period != null && ref.period > 0) {
cfg = {
...cfg,