전략평가 MA 참조에러 수정

This commit is contained in:
Macbook
2026-06-13 01:25:56 +09:00
parent 24128e6a0a
commit 207374a08e
10 changed files with 451 additions and 77 deletions
@@ -463,6 +463,7 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
barSignalHighlight={barSignalHighlight}
hasStrategy={selectedStrategyId != null}
className="seval-signal-panel"
getParams={getEvalParams}
/>
</div>
+31 -16
View File
@@ -24,6 +24,11 @@ import {
candleRangeWindowBars,
formatCandleRangeClause,
} from '../utils/strategyTypes';
import {
appendLegacyMaFieldOption,
buildEmaFieldOptions,
buildMaFieldOptions,
} from '../utils/maDslField';
// ─── 타입 ─────────────────────────────────────────────────────────────────────
type LogicNodeType = 'AND' | 'OR' | 'NOT' | 'CONDITION' | 'TIMEFRAME';
@@ -298,7 +303,7 @@ const ICHIMOKU_CONDS = [
type Opt = { value: string; label: string };
const NONE: Opt = { value: 'NONE', label: '선택안함' };
const getFieldOpts = (ind: string, signalType: 'buy'|'sell', DEF: DefType): Opt[] => {
const getFieldOpts = (ind: string, signalType: 'buy'|'sell', DEF: DefType, cond?: ConditionDSL): Opt[] => {
const overTerm = signalType === 'buy' ? '과열진입' : '과열이탈';
const underTerm = signalType === 'buy' ? '침체이탈' : '침체진입';
const condOpts = (conds: Opt[]): Opt[] => [NONE, ...conds];
@@ -377,16 +382,26 @@ const getFieldOpts = (ind: string, signalType: 'buy'|'sell', DEF: DefType): Opt[
{ value:'VOLUME_VALUE', label:'거래량' },
{ value:'VOLUME_MA', label:'거래량 이동평균' },
]);
case 'MA': return [
NONE,
...DEF.maLines.map((p, i) => ({ value:`MA${p}`, label:`MA${i+1}(${p}일)` })),
{ value:'CLOSE_PRICE', label:'종가' },
];
case 'EMA': return [
NONE,
...DEF.maLines.slice(0,4).map((p, i) => ({ value:`EMA${p}`, label:`EMA${i+1}(${p}일)` })),
{ value:'CLOSE_PRICE', label:'종가' },
];
case 'MA': {
let opts = [
NONE,
...buildMaFieldOptions(DEF.maLines),
{ value: 'CLOSE_PRICE', label: '종가' },
];
if (cond?.leftField) opts = appendLegacyMaFieldOption(opts, cond.leftField);
if (cond?.rightField) opts = appendLegacyMaFieldOption(opts, cond.rightField);
return opts;
}
case 'EMA': {
let opts = [
NONE,
...buildEmaFieldOptions(DEF.maLines, 4),
{ value: 'CLOSE_PRICE', label: '종가' },
];
if (cond?.leftField) opts = appendLegacyMaFieldOption(opts, cond.leftField);
if (cond?.rightField) opts = appendLegacyMaFieldOption(opts, cond.rightField);
return opts;
}
case 'DISPARITY': return condOpts([
{ value:`DISPARITY${DEF.dispUltra}`, label:`초단기 이격도(${DEF.dispUltra}일)` },
{ value:`DISPARITY${DEF.dispShort}`, label:`단기 이격도(${DEF.dispShort}일)` },
@@ -515,8 +530,8 @@ const getDefaultFields = (ind: string, signalType: 'buy'|'sell', DEF: DefType):
DMI: { l:'PDI', r:'MDI' },
OBV: { l:'OBV_LINE', r:'OBV_SIGNAL' },
VOLUME: { l:'VOLUME_VALUE', r:'VOLUME_MA' },
MA: { l:`MA${DEF.maLines[0]}`, r:`MA${DEF.maLines[1]}` },
EMA: { l:`EMA${DEF.maLines[0]}`, r:`EMA${DEF.maLines[1]}` },
MA: { l: 'MA1', r: 'MA2' },
EMA: { l: 'EMA1', r: 'EMA2' },
DISPARITY: { l:`DISPARITY${DEF.dispUltra}`, r: mid(100) },
PSYCHOLOGICAL: { l:'PSY_VALUE', r: over(75) },
NEW_PSYCHOLOGICAL: { l:'NEW_PSY_VALUE', r: over(50) },
@@ -538,7 +553,7 @@ const getDefaultFields = (ind: string, signalType: 'buy'|'sell', DEF: DefType):
// conditionType 변경 시 자동 필드 조정
const applyCondTypeDefaults = (cond: ConditionDSL, newCondType: string, DEF: DefType): ConditionDSL => {
const updated = { ...cond, conditionType: newCondType };
const fieldOpts = getFieldOpts(cond.indicatorType, 'buy', DEF);
const fieldOpts = getFieldOpts(cond.indicatorType, 'buy', DEF, cond);
const isValid = (v: string|undefined) => !!v && fieldOpts.some(o => o.value === v);
const def = getDefaultFields(cond.indicatorType, 'buy', DEF);
@@ -575,7 +590,7 @@ const nodeToText = (node: LogicNode, DEF: DefType = DEF_DEFAULTS): string => {
if (!node) return '';
if (node.type === 'CONDITION' && node.condition) {
const c = node.condition;
const opts = getFieldOpts(c.indicatorType, 'buy', DEF);
const opts = getFieldOpts(c.indicatorType, 'buy', DEF, c);
const L = opts.find(o => o.value === c.leftField)?.label ?? c.leftField ?? c.indicatorType;
const R = opts.find(o => o.value === c.rightField)?.label ?? c.rightField ?? '';
const C = condLabel(c.conditionType);
@@ -680,7 +695,7 @@ interface CondEditorProps {
}
const CondEditor: React.FC<CondEditorProps> = ({ cond, signalType, onChange, def }) => {
const fieldOpts = getFieldOpts(cond.indicatorType, signalType, def);
const fieldOpts = getFieldOpts(cond.indicatorType, signalType, def, cond);
const condOpts = cond.indicatorType === 'ICHIMOKU' ? ICHIMOKU_CONDS : COND_OPTIONS;
const isValid = (v: string|undefined) => !!v && fieldOpts.some(o => o.value === v);
@@ -23,6 +23,8 @@ interface Props {
loadingMessage?: string;
hasStrategy?: boolean;
className?: string;
/** 보조지표 SMA period1~11 — MA 슬롯 라벨·해석용 */
getParams?: (type: string, defaults?: Record<string, number | string | boolean>) => Record<string, number | string | boolean>;
}
interface SideCardProps {
@@ -32,6 +34,7 @@ interface SideCardProps {
signalActive?: boolean;
strategy?: StrategyDto | null;
barTimeSec?: number | null;
getParams?: Props['getParams'];
}
const SideCard: React.FC<SideCardProps> = ({
@@ -41,6 +44,7 @@ const SideCard: React.FC<SideCardProps> = ({
signalActive = false,
strategy,
barTimeSec,
getParams,
}) => {
const [interpretOpen, setInterpretOpen] = useState(false);
const sideLabel = side === 'buy' ? '매수' : '매도';
@@ -48,7 +52,7 @@ const SideCard: React.FC<SideCardProps> = ({
const overallLabel =
overallMet === true ? '충족' : overallMet === false ? '미충족' : null;
const contextMap = useMemo(() => buildConditionContextMap(strategy), [strategy]);
const contextMap = useMemo(() => buildConditionContextMap(strategy, getParams), [strategy, getParams]);
const interpretation = useMemo(
() => buildSideInterpretation(side, metrics, overallMet, contextMap),
[side, metrics, overallMet, contextMap],
@@ -114,6 +118,7 @@ const StrategyEvaluationSignalPanel: React.FC<Props> = ({
loadingMessage,
hasStrategy = true,
className = '',
getParams,
}) => {
const rows = snapshot?.rows ?? [];
const metrics = useMemo(() => buildConditionMetrics(rows), [rows]);
@@ -143,6 +148,7 @@ const StrategyEvaluationSignalPanel: React.FC<Props> = ({
signalActive={barSignalHighlight?.sell ?? false}
strategy={strategy}
barTimeSec={barTimeSec}
getParams={getParams}
/>
<SideCard
side="buy"
@@ -151,6 +157,7 @@ const StrategyEvaluationSignalPanel: React.FC<Props> = ({
signalActive={barSignalHighlight?.buy ?? false}
strategy={strategy}
barTimeSec={barTimeSec}
getParams={getParams}
/>
</div>
);