obv 수정

This commit is contained in:
Macbook
2026-06-09 22:30:19 +09:00
parent 7b4fc1f811
commit 420050dfb7
4 changed files with 31 additions and 7 deletions
@@ -22,6 +22,7 @@ import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
import { useSessionCandleOverlayControls } from '../../hooks/useSessionCandleOverlayControls'; import { useSessionCandleOverlayControls } from '../../hooks/useSessionCandleOverlayControls';
import { ensurePaperChartOverlays } from '../../utils/strategyToChartIndicators'; import { ensurePaperChartOverlays } from '../../utils/strategyToChartIndicators';
import { mergeGlobalDefaultsOntoChartIndicators } from '../../utils/indicatorMainConfig'; import { mergeGlobalDefaultsOntoChartIndicators } from '../../utils/indicatorMainConfig';
import { barsMeetObvVolumeRequirement } from '../../utils/obvChartBars';
import { enrichIndicatorConfig } from '../../utils/indicatorRegistry'; import { enrichIndicatorConfig } from '../../utils/indicatorRegistry';
import { import {
applyNotificationSignalMarkers, applyNotificationSignalMarkers,
@@ -135,7 +136,10 @@ const TradeSignalMiniChart: React.FC<Props> = ({
managerRef.current = mgr; managerRef.current = mgr;
syncToManager(mgr); syncToManager(mgr);
applySignalMarkers(); applySignalMarkers();
}, [syncToManager, applySignalMarkers]); if (needsDeepObv) {
void mgr.repairDualLineSeries();
}
}, [syncToManager, applySignalMarkers, needsDeepObv]);
const applyRealtimeBar = useCallback((bar: OHLCVBar, append: boolean) => { const applyRealtimeBar = useCallback((bar: OHLCVBar, append: boolean) => {
if (barsMarketRef.current !== marketRef.current) return; if (barsMarketRef.current !== marketRef.current) return;
@@ -185,7 +189,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
const obvBarsReady = useMemo(() => { const obvBarsReady = useMemo(() => {
if (!needsDeepObv) return true; if (!needsDeepObv) return true;
if (bars.length < 2) return false; if (bars.length < 2) return false;
return bars.some(b => (Number(b.volume) || 0) > 0); return barsMeetObvVolumeRequirement(bars);
}, [needsDeepObv, bars]); }, [needsDeepObv, bars]);
const chartDataReady = barsReady && obvBarsReady && settingsLoaded; const chartDataReady = barsReady && obvBarsReady && settingsLoaded;
+2
View File
@@ -1203,6 +1203,7 @@ export class ChartManager {
if (this._indicatorLoadStale(loadGen)) return; if (this._indicatorLoadStale(loadGen)) return;
await this.repairDualLineSeries();
this._finalizeIndicatorPaneLayout(); this._finalizeIndicatorPaneLayout();
this._refreshAllIchimokuClouds(); this._refreshAllIchimokuClouds();
this.syncChartOverlayVisibility(); this.syncChartOverlayVisibility();
@@ -1250,6 +1251,7 @@ export class ChartManager {
} }
if (this._indicatorLoadStale(loadGen)) return; if (this._indicatorLoadStale(loadGen)) return;
await this.repairDualLineSeries();
this._finalizeIndicatorPaneLayout(); this._finalizeIndicatorPaneLayout();
} }
+10
View File
@@ -0,0 +1,10 @@
import type { OHLCVBar } from '../types';
/** OBV 본선 계산에 필요한 최소 거래량 품질 (some>0 만으로는 STOMP volume=0 history 통과) */
export function barsMeetObvVolumeRequirement(bars: OHLCVBar[]): boolean {
if (bars.length < 2) return false;
const withVol = bars.filter(b => (Number(b.volume) || 0) > 0).length;
const volSum = bars.reduce((s, b) => s + (Number(b.volume) || 0), 0);
const minBarsWithVol = Math.max(20, Math.floor(bars.length * 0.5));
return volSum > 0 && withVol >= minBarsWithVol;
}
@@ -68,10 +68,14 @@ type GetVisual = (
cloudColors?: import('./ichimokuConfig').IchimokuCloudColors; cloudColors?: import('./ichimokuConfig').IchimokuCloudColors;
}; };
let _idSeq = 0; /** 전략 DSL 기준 안정 id — getVisualConfig/settingsRevision 변경 시 id 재발급 방지 */
function newIndId(): string { export function stableStrategyIndicatorId(ref: IndicatorRef): string {
_idSeq += 1; const parts = [ref.registryType];
return `vstrat_${_idSeq}_${Date.now()}`; if (ref.period != null) parts.push(`p${ref.period}`);
if (ref.leftPeriod != null) parts.push(`l${ref.leftPeriod}`);
if (ref.rightPeriod != null) parts.push(`r${ref.rightPeriod}`);
if (ref.targetValue != null) parts.push(`t${ref.targetValue}`);
return `strat_${parts.join('_')}`;
} }
export function candleTypeToTimeframe(candleType: string): Timeframe { export function candleTypeToTimeframe(candleType: string): Timeframe {
@@ -171,6 +175,7 @@ function buildOneIndicator(
ref: IndicatorRef, ref: IndicatorRef,
getParams: GetParams, getParams: GetParams,
getVisual: GetVisual, getVisual: GetVisual,
stableId?: string,
): IndicatorConfig | null { ): IndicatorConfig | null {
const def = getIndicatorDef(ref.registryType); const def = getIndicatorDef(ref.registryType);
if (!def) return null; if (!def) return null;
@@ -179,7 +184,7 @@ function buildOneIndicator(
const { plots, hlines, cloudColors } = getVisual(ref.registryType, def.plots, def.hlines ?? []); const { plots, hlines, cloudColors } = getVisual(ref.registryType, def.plots, def.hlines ?? []);
let cfg: IndicatorConfig = { let cfg: IndicatorConfig = {
id: newIndId(), id: stableId ?? stableStrategyIndicatorId(ref),
type: def.type, type: def.type,
params: { ...params }, params: { ...params },
plots, plots,
@@ -295,6 +300,7 @@ export function ensurePaperChartOverlays(
{ dslType: 'MA', registryType: 'SMA' }, { dslType: 'MA', registryType: 'SMA' },
getParams, getParams,
getVisual, getVisual,
'strat_overlay_SMA',
); );
if (sma) prepend.push(sma); if (sma) prepend.push(sma);
} }
@@ -303,6 +309,7 @@ export function ensurePaperChartOverlays(
{ dslType: 'ICHIMOKU', registryType: 'IchimokuCloud' }, { dslType: 'ICHIMOKU', registryType: 'IchimokuCloud' },
getParams, getParams,
getVisual, getVisual,
'strat_overlay_IchimokuCloud',
); );
if (ichimoku) prepend.push(ichimoku); if (ichimoku) prepend.push(ichimoku);
} }
@@ -311,6 +318,7 @@ export function ensurePaperChartOverlays(
{ dslType: 'BOLLINGER', registryType: 'BollingerBands' }, { dslType: 'BOLLINGER', registryType: 'BollingerBands' },
getParams, getParams,
getVisual, getVisual,
'strat_overlay_BollingerBands',
); );
if (bb) prepend.push(bb); if (bb) prepend.push(bb);
} }