전략조건 설명 오류 수정

This commit is contained in:
Macbook
2026-06-19 01:32:43 +09:00
parent 5bbc105326
commit 8bceadc55b
@@ -215,16 +215,97 @@ function describeCondition(
return `${left}에 대해 「${label}」 조건이 성립하는 경우`;
}
function describeNode(
function describeConditionNode(
node: LogicNode,
signalType: 'buy' | 'sell',
def: DefType,
depth = 0,
): string {
if (node.type !== 'CONDITION' || !node.condition) return '';
return describeCondition(normalizeCompositeCondition(node.condition), signalType, def);
}
/** AND 그룹 — 자식이 모두 단일 조건이면 묶음으로, 아니면 하위 트리 재귀 */
function describeAndGroup(
children: LogicNode[],
signalType: 'buy' | 'sell',
def: DefType,
indent: string,
options?: { showCountHeader?: boolean },
): string[] {
if (children.length === 0) return [`${indent}(비어 있는 AND 그룹)`];
if (children.length === 1) {
return describeLogicTree(children[0], signalType, def, indent, false);
}
const allSimple = children.every(c => c.type === 'CONDITION');
const lines: string[] = [];
if (options?.showCountHeader !== false && allSimple) {
lines.push(`${indent}(아래 ${children.length}개 조건을 모두 만족)`);
} else if (!allSimple) {
lines.push(`${indent}다음을 모두 동시에 만족:`);
}
if (allSimple) {
for (const child of children) {
const text = describeConditionNode(child, signalType, def);
if (text) lines.push(`${indent}${text}`);
}
return lines;
}
for (const child of children) {
lines.push(...describeLogicTree(child, signalType, def, indent + ' ', false));
}
return lines;
}
/** OR 그룹 — 각 자식을 [경로 N]으로 구분, AND 자식은 묶음으로 표현 */
function describeOrGroup(
children: LogicNode[],
signalType: 'buy' | 'sell',
def: DefType,
indent: string,
atRoot: boolean,
): string[] {
if (children.length === 0) return [`${indent}(비어 있는 OR 그룹)`];
if (children.length === 1) {
return describeLogicTree(children[0], signalType, def, indent, atRoot);
}
const lines: string[] = [];
if (atRoot) {
lines.push('다음 경로 중 하나가 충족되면 됩니다.');
} else {
lines.push(`${indent}다음 중 하나:`);
}
children.forEach((child, idx) => {
if (idx > 0) lines.push(`${indent}또는`);
lines.push(`${indent}[경로 ${idx + 1}]`);
if (child.type === 'AND') {
lines.push(...describeAndGroup(child.children ?? [], signalType, def, indent + ' '));
} else if (child.type === 'CONDITION') {
const text = describeConditionNode(child, signalType, def);
if (text) lines.push(`${indent}${text}`);
} else {
lines.push(...describeLogicTree(child, signalType, def, indent + ' ', false));
}
});
return lines;
}
function describeLogicTree(
node: LogicNode,
signalType: 'buy' | 'sell',
def: DefType,
indent = '',
atRoot = false,
): string[] {
if (node.type === 'CONDITION' && node.condition) {
const c = normalizeCompositeCondition(node.condition);
const line = describeCondition(c, signalType, def);
return [depth > 0 ? `${line}` : line];
const line = describeCondition(normalizeCompositeCondition(node.condition), signalType, def);
return [`${indent}${line}`];
}
if (node.type === 'TIMEFRAME') {
@@ -235,47 +316,66 @@ function describeNode(
const tf = types.length > 1
? types.map(candleKo).join(', ')
: candleKo(types[0]);
if (!inner) return [`[${tf}] 연결된 조건이 없습니다.`];
const innerLines = describeNode(inner, signalType, def, depth + 1);
return [`[${tf} 기준]`, ...innerLines.map(l => (l.startsWith('•') ? ` ${l}` : `${l}`))];
if (!inner) return [`${indent}[${tf}] 연결된 조건이 없습니다.`];
const innerLines = describeLogicTree(inner, signalType, def, indent + ' ', false);
return [`${indent}[${tf} 기준]`, ...innerLines];
}
if (node.type === 'AND') {
const children = node.children ?? [];
if (children.length === 0) return ['(비어 있는 AND 그룹)'];
if (children.length === 0) return [`${indent}(비어 있는 AND 그룹)`];
if (children.length === 1) {
return describeLogicTree(children[0], signalType, def, indent, atRoot);
}
const hasOrChild = children.some(c => c.type === 'OR');
if (hasOrChild) {
const lines: string[] = [];
if (atRoot) lines.push('다음 조건을 모두 동시에 만족해야 합니다.');
else lines.push(`${indent}다음을 모두 동시에 만족:`);
for (const child of children) {
lines.push(...describeLogicTree(child, signalType, def, indent + ' ', false));
}
return lines;
}
const lines: string[] = [];
if (depth === 0 && children.length > 1) {
if (atRoot) {
lines.push('다음 조건을 모두 동시에 만족해야 합니다.');
}
children.forEach(child => {
lines.push(...describeNode(child, signalType, def, depth + 1));
});
lines.push(...describeAndGroup(children, signalType, def, indent, {
showCountHeader: !atRoot,
}));
return lines;
}
if (node.type === 'OR') {
const children = node.children ?? [];
if (children.length === 0) return ['(비어 있는 OR 그룹)'];
const lines: string[] = [];
if (depth === 0 && children.length > 1) {
lines.push('다음 조건 중 하나라도 만족하면 됩니다.');
}
children.forEach(child => {
lines.push(...describeNode(child, signalType, def, depth + 1));
});
return lines;
return describeOrGroup(node.children ?? [], signalType, def, indent, atRoot);
}
if (node.type === 'NOT') {
const child = node.children?.[0];
if (!child) return ['(비어 있는 NOT 그룹)'];
const sub = describeNode(child, signalType, def, depth + 1);
return ['다음 조건이 성립하지 않을 때:', ...sub.map(l => ` (부정) ${l.replace(/^•\s*/, '')}`)];
if (!child) return [`${indent}(비어 있는 NOT 그룹)`];
const sub = describeLogicTree(child, signalType, def, indent + ' ', false);
return [
`${indent}다음 조건이 성립하지 않을 때:`,
...sub.map(l => `${indent} (부정) ${l.replace(/^[\s•]+/, '')}`),
];
}
return [];
}
function describeNode(
node: LogicNode,
signalType: 'buy' | 'sell',
def: DefType,
depth = 0,
): string[] {
const indent = depth > 0 ? ' '.repeat(depth) : '';
return describeLogicTree(node, signalType, def, indent, depth === 0);
}
function describeSignalBranches(
editorState: EditorConditionState | undefined,
fallbackRoot: LogicNode | null,
@@ -306,7 +406,7 @@ function describeSignalBranches(
? `${tf} 각 시간봉 마감 시점마다 아래 조건을 독립적으로 평가합니다.`
: `${tf} 차트를 기준으로 아래 조건을 평가합니다.`,
);
bullets.push(...describeNode(root!, signalType, def));
bullets.push(...describeLogicTree(root!, signalType, def, '', true));
return { hasContent: true, paragraphs, bullets };
}
@@ -321,9 +421,7 @@ function describeSignalBranches(
const tf = candleKo(branch.candleType);
bullets.push(`[시간봉 ${idx + 1}: ${tf}]`);
if (branch.root) {
bullets.push(...describeNode(branch.root, signalType, def, 1).map(l =>
l.startsWith('•') ? ` ${l}` : `${l}`,
));
bullets.push(...describeLogicTree(branch.root, signalType, def, ' ', false));
}
});