obv 신호선
This commit is contained in:
@@ -22,7 +22,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useRef, useCallback, useState } from 'react';
|
import { useEffect, useRef, useCallback, useState } from 'react';
|
||||||
import { getIndicatorDef, mergePlotDefs, normalizeHLines, PlotDef, HLineDef } from '../utils/indicatorRegistry';
|
import { getIndicatorDef, mergePlotDefs, normalizeHLines, normalizeObvParams, PlotDef, HLineDef } from '../utils/indicatorRegistry';
|
||||||
import { createDefaultSmaParams, createDefaultSmaPlots, normalizeSmaConfig } from '../utils/smaConfig';
|
import { createDefaultSmaParams, createDefaultSmaPlots, normalizeSmaConfig } from '../utils/smaConfig';
|
||||||
import { normalizeIchimokuConfig, mergeIchimokuPlots } from '../utils/ichimokuConfig';
|
import { normalizeIchimokuConfig, mergeIchimokuPlots } from '../utils/ichimokuConfig';
|
||||||
import {
|
import {
|
||||||
@@ -177,6 +177,9 @@ export function useIndicatorSettings(sessionKey = 0) {
|
|||||||
if (type === 'IchimokuCloud') {
|
if (type === 'IchimokuCloud') {
|
||||||
return normalizeIchimokuConfig({ id: '', type: 'IchimokuCloud', params: merged }).params;
|
return normalizeIchimokuConfig({ id: '', type: 'IchimokuCloud', params: merged }).params;
|
||||||
}
|
}
|
||||||
|
if (type === 'OBV') {
|
||||||
|
return normalizeObvParams(merged);
|
||||||
|
}
|
||||||
return merged;
|
return merged;
|
||||||
},
|
},
|
||||||
[settingsRevision],
|
[settingsRevision],
|
||||||
|
|||||||
@@ -791,7 +791,7 @@ export class ChartManager {
|
|||||||
const showSeriesTitle = pane < 2 && showPriceLabel;
|
const showSeriesTitle = pane < 2 && showPriceLabel;
|
||||||
series = this.chart.addSeries(LineSeries, {
|
series = this.chart.addSeries(LineSeries, {
|
||||||
color: plotDef.color,
|
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),
|
lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
|
||||||
priceLineVisible: false,
|
priceLineVisible: false,
|
||||||
lastValueVisible: showPriceLabel,
|
lastValueVisible: showPriceLabel,
|
||||||
|
|||||||
@@ -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 종류 (단순·지수·가중만) */
|
/** 업비트 OBV 신호선 MA 종류 (단순·지수·가중만) */
|
||||||
export const OBV_MA_TYPE_OPTIONS = ['SMA', 'EMA', 'WMA'] as const;
|
export const OBV_MA_TYPE_OPTIONS = ['SMA', 'EMA', 'WMA'] as const;
|
||||||
export type ObvMaType = (typeof OBV_MA_TYPE_OPTIONS)[number];
|
export type ObvMaType = (typeof OBV_MA_TYPE_OPTIONS)[number];
|
||||||
@@ -520,18 +559,20 @@ export function enrichIndicatorConfig(
|
|||||||
if (cfgNorm.type === 'DMI') {
|
if (cfgNorm.type === 'DMI') {
|
||||||
params = normalizeDmiParams(params);
|
params = normalizeDmiParams(params);
|
||||||
}
|
}
|
||||||
const plots = cfgNorm.type === 'BollingerBands'
|
let plots = cfgNorm.type === 'BollingerBands'
|
||||||
? mergeBbPlots(cfgNorm.plots, def.plots)
|
? mergeBbPlots(cfgNorm.plots, def.plots)
|
||||||
: mergePlotDefs(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) {
|
for (const p of plots) {
|
||||||
if (plotVisibility[p.id] === undefined) plotVisibility[p.id] = true;
|
if (plotVisibility[p.id] === undefined) plotVisibility[p.id] = true;
|
||||||
}
|
}
|
||||||
if (cfgNorm.type === 'OBV') {
|
({ plots, plotVisibility } = normalizeSignalLineIndicatorPlots(
|
||||||
plotVisibility.plot1 = plotVisibility.plot1 !== false;
|
cfgNorm.type,
|
||||||
} else if ((cfgNorm.type === 'CCI' || cfgNorm.type === 'RSI') && params.maType === 'None') {
|
plots,
|
||||||
plotVisibility.plot1 = false;
|
def.plots,
|
||||||
}
|
plotVisibility,
|
||||||
|
params,
|
||||||
|
));
|
||||||
|
|
||||||
const out: import('../types').IndicatorConfig = {
|
const out: import('../types').IndicatorConfig = {
|
||||||
...cfgNorm,
|
...cfgNorm,
|
||||||
@@ -636,6 +677,9 @@ export async function calculateIndicator(
|
|||||||
if (type === 'CCI') {
|
if (type === 'CCI') {
|
||||||
calcParams = normalizeCciParams(params);
|
calcParams = normalizeCciParams(params);
|
||||||
}
|
}
|
||||||
|
if (type === 'OBV') {
|
||||||
|
calcParams = normalizeObvParams(params);
|
||||||
|
}
|
||||||
if (type === 'IchimokuCloud') {
|
if (type === 'IchimokuCloud') {
|
||||||
const { senkou } = resolveIchimokuDisplacements(params);
|
const { senkou } = resolveIchimokuDisplacements(params);
|
||||||
calcParams = { ...params, displacement: senkou };
|
calcParams = { ...params, displacement: senkou };
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
enrichIndicatorConfig,
|
enrichIndicatorConfig,
|
||||||
getIndicatorDef,
|
getIndicatorDef,
|
||||||
getHLineLabel,
|
getHLineLabel,
|
||||||
|
mergePlotDefs,
|
||||||
|
normalizeObvParams,
|
||||||
type HLineDef,
|
type HLineDef,
|
||||||
} from './indicatorRegistry';
|
} from './indicatorRegistry';
|
||||||
import { buildMainIndicatorConfig } from './indicatorMainConfig';
|
import { buildMainIndicatorConfig } from './indicatorMainConfig';
|
||||||
@@ -199,6 +201,17 @@ function buildOneIndicator(
|
|||||||
plots: mainSma?.plots ?? cfg.plots,
|
plots: mainSma?.plots ?? cfg.plots,
|
||||||
plotVisibility: mainSma?.plotVisibility ?? cfg.plotVisibility,
|
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) {
|
} else if (ref.period != null && ref.period > 0) {
|
||||||
cfg = {
|
cfg = {
|
||||||
...cfg,
|
...cfg,
|
||||||
|
|||||||
Reference in New Issue
Block a user