전략편집기 수정, 보조지표 설정 수정

This commit is contained in:
Macbook
2026-05-27 23:59:30 +09:00
parent 8cc0d1c88c
commit 2713b2951d
17 changed files with 210 additions and 46 deletions
+1 -4
View File
@@ -1298,7 +1298,7 @@ export class ChartManager {
.find(p => p.id === meta.plotId);
const prevVal = plotData.length >= 2 ? plotData[plotData.length - 2]?.value : null;
const barColor = plotDef
? histogramBarColor(curVal as number, prevVal, plotDef, lastPoint.color)
? histogramBarColor(curVal as number, prevVal, plotDef)
: (lastPoint.color ?? '#26A69A88');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(entry.seriesList[i] as ISeriesApi<any>).update({
@@ -2044,9 +2044,6 @@ export class ChartManager {
if (entry.seriesMeta.some(m => m.isHistogram)) {
void this._repaintHistogramSeries(config.id, config);
}
if (entry.seriesMeta.some(m => m.isHistogram)) {
void this._repaintHistogramSeries(config.id, config);
}
if (this._candleOnlyLayout && this._isSubPaneIndicator(entry)) {
this._hideSubPaneIndicator(entry);
+14 -3
View File
@@ -168,9 +168,20 @@ export function hasEditableThreshold(cond: ConditionDSL): boolean {
|| parseThresholdField(cond.leftField) != null;
}
export function getConditionThreshold(cond: ConditionDSL, side: 'left' | 'right'): number | null {
if (side === 'right' && !isThresholdOverridden(cond)) {
return parseThresholdField(cond.rightField);
/**
* 임계값 표시 — thresholdOverride 가 false 이면 inheritedField(보조지표 설정 DEF) 우선.
*/
export function getConditionThreshold(
cond: ConditionDSL,
side: 'left' | 'right',
inheritedField?: string,
): number | null {
if (!isThresholdOverridden(cond) && inheritedField) {
const fromDef = parseThresholdField(inheritedField);
if (fromDef != null) return fromDef;
}
if (isThresholdOverridden(cond) && cond.targetValue != null && Number.isFinite(cond.targetValue)) {
return cond.targetValue;
}
const field = side === 'left' ? cond.leftField : cond.rightField;
return parseThresholdField(field);
+2 -3
View File
@@ -73,9 +73,7 @@ export function histogramBarColor(
value: number,
prev: number | null | undefined,
plot: PlotDef,
pointColor?: string,
): string {
if (pointColor) return withHistogramAlpha(pointColor);
const isDown = prev != null && !isNaN(prev) && value < prev;
const isNeg = value < 0;
const raw = (isDown || isNeg) ? resolveHistogramDownColor(plot) : resolveHistogramUpColor(plot);
@@ -89,6 +87,7 @@ export function mapHistogramSeriesData(
return filtered.map((p, idx, arr) => ({
time: p.time as import('lightweight-charts').Time,
value: p.value,
color: histogramBarColor(p.value, idx > 0 ? arr[idx - 1].value : null, plot, p.color),
// lightweight-charts-indicators MACD 등이 막대별 color를 넣어도 설정(상승/하락) 색을 우선
color: histogramBarColor(p.value, idx > 0 ? arr[idx - 1].value : null, plot),
}));
}
@@ -264,7 +264,6 @@ function wrapTimeframe(candleType: string, root: LogicNode): LogicNode {
function wrapTimeframes(candleTypes: string[], root: LogicNode): LogicNode {
const types = normalizeCandleTypesList(candleTypes);
if (types.length === 1) return wrapTimeframe(types[0], root);
return {
id: generateNodeId(),
type: 'TIMEFRAME',
+9 -15
View File
@@ -1,7 +1,7 @@
/** Shared strategy editor utilities — extracted from StrategyPage */
import React from 'react';
import type { IndicatorConfig } from '../types/index';
import { getIndicatorDef, getHLineLabel, type PlotDef, type HLineDef } from '../utils/indicatorRegistry';
import { getIndicatorDef, getHLineLabel, INDICATOR_REGISTRY, type PlotDef, type HLineDef } from '../utils/indicatorRegistry';
import type { LogicNode, LogicNodeType, ConditionDSL } from '../utils/strategyTypes';
import {
compositeDisplayName,
@@ -270,24 +270,17 @@ export function buildStrategyEditorDefFromSettings(
defaultHlines?: HLineDef[],
) => { plots: PlotDef[]; hlines: HLineDef[]; cloudColors?: unknown },
): DefType {
const registryTypes = [
'RSI', 'MACD', 'CCI', 'Stochastic', 'ADX', 'TRIX', 'DMI', 'BollingerBands',
'IchimokuCloud', 'WilliamsPercentRange', 'VolumeOscillator', 'VR', 'Disparity',
'Psychological', 'InvestPsychological', 'OBV', 'DonchianChannels', 'PriceExtreme',
'SMA',
];
const configs: IndicatorConfig[] = [];
for (const type of registryTypes) {
const def = getIndicatorDef(type);
if (!def) continue;
const params = getParams(type, def.defaultParams as Record<string, number | string | boolean>);
const { plots, hlines, cloudColors } = getVisualConfig(type, def.plots, def.hlines);
for (const regDef of INDICATOR_REGISTRY) {
const type = regDef.type;
const params = getParams(type, regDef.defaultParams as Record<string, number | string | boolean>);
const { plots, hlines, cloudColors } = getVisualConfig(type, regDef.plots, regDef.hlines);
configs.push({
id: `se-def-${type}`,
type,
params: { ...params },
plots,
hlines,
plots: plots.map(p => ({ ...p })),
hlines: hlines.map(h => ({ ...h })),
...(cloudColors ? { cloudColors } : {}),
} as IndicatorConfig);
}
@@ -800,6 +793,7 @@ export const CondEditor: React.FC<CondEditorProps> = ({ cond, signalType, onChan
const isValid = (v: string|undefined) => !!v && fieldOpts.some(o => o.value === v);
const defaults = getDefaultConditionFields(normalized.indicatorType, signalType, def);
const inheritedThresholdField = defaults.r;
const getLeftValue = () => {
const v = resolveFieldOptionValue(normalized.indicatorType, normalized.leftField);
@@ -1011,7 +1005,7 @@ export const CondEditor: React.FC<CondEditorProps> = ({ cond, signalType, onChan
fieldValue={rightValue}
isPresetField={fieldOpts.some(o => o.value === normalized.rightField)}
customKind={resolveFieldCustomKind(normalized.indicatorType, normalized.rightField)}
customNumber={getConditionThreshold(normalized, 'right')
customNumber={getConditionThreshold(normalized, 'right', inheritedThresholdField)
?? parseThresholdField(normalized.rightField)
?? 0}
numberPresets={getThresholdPresetOptions(normalized.indicatorType)}