import React from 'react'; import type { LogicNode } from '../../utils/strategyTypes'; import { nodeToText, type DefType } from '../../utils/strategyEditorShared'; import { getStrategyIndicatorDisplayName } from '../../utils/strategyPaletteStorage'; import { getIndicatorPaletteCategory } from './paletteCategories'; import { collectEditorBranches, normalizeStartCombineOp, type EditorConditionState, } from '../../utils/strategyConditionSerde'; import { formatStartCandleLabel } from '../../utils/strategyStartNodes'; interface Props { buyCondition: LogicNode | null; sellCondition: LogicNode | null; buyEditorState?: EditorConditionState; sellEditorState?: EditorConditionState; orphanCount: number; def: DefType; } function renderCondition(node: LogicNode, def: DefType): React.ReactNode { const full = nodeToText(node, def); const ind = node.condition?.indicatorType ?? ''; const indLabel = getStrategyIndicatorDisplayName(ind); const prefix = `${indLabel} -`; const tail = indLabel && full.startsWith(prefix) ? full.slice(prefix.length) : full; const cat = getIndicatorPaletteCategory(ind); return ( <> ( {indLabel ? ( <> {indLabel} {tail ? {tail.startsWith(' ') ? tail : ` ${tail}`} : null} ) : ( {full || '?'} )} ) ); } function renderNode(node: LogicNode, def: DefType): React.ReactNode { if (node.type === 'CONDITION') return renderCondition(node, def); if (node.type === 'AND') { const children = node.children ?? []; if (children.length === 0) return (빈 AND); return ( <> ( {children.map((child, i) => ( {i > 0 ? AND : null} {renderNode(child, def)} ))} ) ); } if (node.type === 'OR') { const children = node.children ?? []; if (children.length === 0) return (빈 OR); return ( <> ( {children.map((child, i) => ( {i > 0 ? OR : null} {renderNode(child, def)} ))} ) ); } if (node.type === 'NOT') { const child = node.children?.[0]; return child ? ( <> NOT {renderNode(child, def)} ) : ( (빈 NOT) ); } if (node.type === 'TIMEFRAME') { const inner = node.children?.[0]; const types = node.candleTypes?.length ? node.candleTypes : [node.candleType ?? '1m']; const tf = types.length > 1 ? types.map(formatStartCandleLabel).join(' · ') : formatStartCandleLabel(types[0]); return inner ? ( <> [{tf}] {' '} {renderNode(inner, def)} ) : ( [{tf} 빈 조건] ); } return null; } function renderSignalBranches( editorState: EditorConditionState | undefined, fallbackRoot: LogicNode | null, def: DefType, ): React.ReactNode { const branches = editorState ? collectEditorBranches(editorState) : fallbackRoot ? [{ candleType: '1m', root: fallbackRoot }] : []; if (branches.length === 0) { return (연결된 조건 없음); } if (branches.length === 1) { const { candleType, candleTypes, root } = branches[0]; if (!root) return (연결된 조건 없음); const tfLabel = candleTypes && candleTypes.length > 1 ? candleTypes.map(formatStartCandleLabel).join(' · ') : formatStartCandleLabel(candleType); return ( <> [{tfLabel}] {' '} {renderNode(root, def)} ); } const combineOp = normalizeStartCombineOp(editorState?.startCombineOp); const combineClass = combineOp === 'AND' ? 'se-formula-logic--and' : 'se-formula-logic--or'; return ( <> ( {branches.map((branch, i) => ( {i > 0 ? ( {combineOp} ) : null} {branch.root ? ( <> [{formatStartCandleLabel(branch.candleType)}] {' '} {renderNode(branch.root, def)} ) : ( [{formatStartCandleLabel(branch.candleType)} 빈 조건] )} ))} ) ); } export default function LogicExpressionPreview({ buyCondition, sellCondition, buyEditorState, sellEditorState, orphanCount, def, }: Props) { const hasBody = buyCondition || sellCondition || (buyEditorState && collectEditorBranches(buyEditorState).some(b => b.root)) || (sellEditorState && collectEditorBranches(sellEditorState).some(b => b.root)); return (
{!hasBody && ( (연결된 조건을 구성하세요) )} {(buyCondition || buyEditorState) && (
[매수] {' '} {renderSignalBranches(buyEditorState, buyCondition, def)}
)} {(sellCondition || sellEditorState) && (
[매도] {' '} {renderSignalBranches(sellEditorState, sellCondition, def)}
)} {orphanCount > 0 && (
{'// 미연결 요소 '}{orphanCount}{'개 — 전략 코드 미포함'}
)}
); }