전략조건 설명 오류 수정
This commit is contained in:
@@ -215,16 +215,97 @@ function describeCondition(
|
|||||||
return `${left}에 대해 「${label}」 조건이 성립하는 경우`;
|
return `${left}에 대해 「${label}」 조건이 성립하는 경우`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function describeNode(
|
function describeConditionNode(
|
||||||
node: LogicNode,
|
node: LogicNode,
|
||||||
signalType: 'buy' | 'sell',
|
signalType: 'buy' | 'sell',
|
||||||
def: DefType,
|
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[] {
|
): string[] {
|
||||||
if (node.type === 'CONDITION' && node.condition) {
|
if (node.type === 'CONDITION' && node.condition) {
|
||||||
const c = normalizeCompositeCondition(node.condition);
|
const line = describeCondition(normalizeCompositeCondition(node.condition), signalType, def);
|
||||||
const line = describeCondition(c, signalType, def);
|
return [`${indent}• ${line}`];
|
||||||
return [depth > 0 ? `• ${line}` : line];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.type === 'TIMEFRAME') {
|
if (node.type === 'TIMEFRAME') {
|
||||||
@@ -235,47 +316,66 @@ function describeNode(
|
|||||||
const tf = types.length > 1
|
const tf = types.length > 1
|
||||||
? types.map(candleKo).join(', ')
|
? types.map(candleKo).join(', ')
|
||||||
: candleKo(types[0]);
|
: candleKo(types[0]);
|
||||||
if (!inner) return [`[${tf}] 연결된 조건이 없습니다.`];
|
if (!inner) return [`${indent}[${tf}] 연결된 조건이 없습니다.`];
|
||||||
const innerLines = describeNode(inner, signalType, def, depth + 1);
|
const innerLines = describeLogicTree(inner, signalType, def, indent + ' ', false);
|
||||||
return [`[${tf} 기준]`, ...innerLines.map(l => (l.startsWith('•') ? ` ${l}` : ` • ${l}`))];
|
return [`${indent}[${tf} 기준]`, ...innerLines];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.type === 'AND') {
|
if (node.type === 'AND') {
|
||||||
const children = node.children ?? [];
|
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[] = [];
|
const lines: string[] = [];
|
||||||
if (depth === 0 && children.length > 1) {
|
if (atRoot) {
|
||||||
lines.push('다음 조건을 모두 동시에 만족해야 합니다.');
|
lines.push('다음 조건을 모두 동시에 만족해야 합니다.');
|
||||||
}
|
}
|
||||||
children.forEach(child => {
|
lines.push(...describeAndGroup(children, signalType, def, indent, {
|
||||||
lines.push(...describeNode(child, signalType, def, depth + 1));
|
showCountHeader: !atRoot,
|
||||||
});
|
}));
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.type === 'OR') {
|
if (node.type === 'OR') {
|
||||||
const children = node.children ?? [];
|
return describeOrGroup(node.children ?? [], signalType, def, indent, atRoot);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.type === 'NOT') {
|
if (node.type === 'NOT') {
|
||||||
const child = node.children?.[0];
|
const child = node.children?.[0];
|
||||||
if (!child) return ['(비어 있는 NOT 그룹)'];
|
if (!child) return [`${indent}(비어 있는 NOT 그룹)`];
|
||||||
const sub = describeNode(child, signalType, def, depth + 1);
|
const sub = describeLogicTree(child, signalType, def, indent + ' ', false);
|
||||||
return ['다음 조건이 성립하지 않을 때:', ...sub.map(l => ` (부정) ${l.replace(/^•\s*/, '')}`)];
|
return [
|
||||||
|
`${indent}다음 조건이 성립하지 않을 때:`,
|
||||||
|
...sub.map(l => `${indent} (부정) ${l.replace(/^[\s•]+/, '')}`),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
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(
|
function describeSignalBranches(
|
||||||
editorState: EditorConditionState | undefined,
|
editorState: EditorConditionState | undefined,
|
||||||
fallbackRoot: LogicNode | null,
|
fallbackRoot: LogicNode | null,
|
||||||
@@ -306,7 +406,7 @@ function describeSignalBranches(
|
|||||||
? `${tf} 각 시간봉 마감 시점마다 아래 조건을 독립적으로 평가합니다.`
|
? `${tf} 각 시간봉 마감 시점마다 아래 조건을 독립적으로 평가합니다.`
|
||||||
: `${tf} 차트를 기준으로 아래 조건을 평가합니다.`,
|
: `${tf} 차트를 기준으로 아래 조건을 평가합니다.`,
|
||||||
);
|
);
|
||||||
bullets.push(...describeNode(root!, signalType, def));
|
bullets.push(...describeLogicTree(root!, signalType, def, '', true));
|
||||||
return { hasContent: true, paragraphs, bullets };
|
return { hasContent: true, paragraphs, bullets };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,9 +421,7 @@ function describeSignalBranches(
|
|||||||
const tf = candleKo(branch.candleType);
|
const tf = candleKo(branch.candleType);
|
||||||
bullets.push(`[시간봉 ${idx + 1}: ${tf}]`);
|
bullets.push(`[시간봉 ${idx + 1}: ${tf}]`);
|
||||||
if (branch.root) {
|
if (branch.root) {
|
||||||
bullets.push(...describeNode(branch.root, signalType, def, 1).map(l =>
|
bullets.push(...describeLogicTree(branch.root, signalType, def, ' ', false));
|
||||||
l.startsWith('•') ? ` ${l}` : ` • ${l}`,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user