전략조건 설명 오류 수정
This commit is contained in:
@@ -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 (depth === 0 && children.length > 1) {
|
||||
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 (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));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user