diff --git a/frontend/src/components/StrategyEvaluationPage.tsx b/frontend/src/components/StrategyEvaluationPage.tsx index 632f54c..e43a784 100644 --- a/frontend/src/components/StrategyEvaluationPage.tsx +++ b/frontend/src/components/StrategyEvaluationPage.tsx @@ -53,7 +53,7 @@ import StrategyEvaluationAiVerifyPanel from './strategyEvaluation/StrategyEvalua import { buildStrategyEvaluationAiVerifyContext, requestStrategyEvaluationAiVerify, - summarizeVerifyContext, + buildAiPanelSelectionSummary, } from '../utils/strategyEvaluationAiVerify'; import type { StrategyEvaluationAiVerifyResponse } from '../utils/backendApi'; @@ -106,7 +106,6 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) { const [aiVerifyError, setAiVerifyError] = useState(null); const [aiVerifyResult, setAiVerifyResult] = useState(null); const [aiVerifyContext, setAiVerifyContext] = useState(null); - const [aiVerifySummary, setAiVerifySummary] = useState(null); const aiVerifyGenRef = useRef(0); const rightTabRef = useRef('signal'); const aiVerifyPollAbortRef = useRef(null); @@ -298,6 +297,19 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) { [backtestSignals, selectedBarTimeSec, selectedBarIndex], ); + const aiPanelSelectionSummary = useMemo( + () => buildAiPanelSelectionSummary({ + market, + timeframe: chartTimeframe, + bars, + selectedBarIndex, + snapshot, + barSignalHighlight, + strategyName: selectedStrategy?.name ?? null, + }), + [market, chartTimeframe, bars, selectedBarIndex, snapshot, barSignalHighlight, selectedStrategy?.name], + ); + const handleSignalsChange = useCallback((signals: BacktestSignal[]) => { setBacktestSignals(signals); }, []); @@ -439,7 +451,6 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) { indicatorParams, evaluationBarCount: Math.min(BACKTEST_DISPLAY_BAR_COUNT, bars.length), }); - setAiVerifySummary(summarizeVerifyContext(context)); setAiVerifyContext(context); const result = await requestStrategyEvaluationAiVerify(context, { @@ -863,7 +874,7 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) { = ({ bundle export interface StrategyEvaluationAiVerifyPanelProps { loading: boolean; error: string | null; - contextSummary: string | null; + /** 차트 선택 봉·평가 요약 (실시간) */ + selectionSummary: string | null; verifyContext: StrategyEvaluationAiVerifyContext | null; result: StrategyEvaluationAiVerifyResponse | null; runDisabled?: boolean; @@ -217,7 +218,7 @@ export interface StrategyEvaluationAiVerifyPanelProps { const StrategyEvaluationAiVerifyPanel: React.FC = ({ loading, error, - contextSummary, + selectionSummary, verifyContext, result, runDisabled = false, @@ -238,18 +239,15 @@ const StrategyEvaluationAiVerifyPanel: React.FC
- {contextSummary && ( -

{contextSummary}

- )} - {!contextSummary && !hasContent && ( + {selectionSummary ? ( +
{selectionSummary}
+ ) : (

- 차트 툴바의 AI 검증 버튼 또는 아래 실행으로 선택 봉 기준 LLM 분석을 시작합니다. + 차트에서 봉을 선택하면 해당 캔들 정보가 표시됩니다.

)}
@@ -265,7 +263,7 @@ const StrategyEvaluationAiVerifyPanel: React.FC ) : ( - result ? '다시 분석' : '분석 실행' + 'AI 분석' )}
diff --git a/frontend/src/styles/strategyEvaluation.css b/frontend/src/styles/strategyEvaluation.css index 1c0bd9e..a852772 100644 --- a/frontend/src/styles/strategyEvaluation.css +++ b/frontend/src/styles/strategyEvaluation.css @@ -1203,6 +1203,16 @@ color: var(--se-text-muted, #8b949e); } +.seval-ai-verify-panel-selection { + margin: 0; + font-family: inherit; + font-size: 0.72rem; + line-height: 1.5; + color: var(--se-text-muted, #8b949e); + white-space: pre-wrap; + word-break: break-word; +} + .seval-ai-verify-panel-run-btn { flex-shrink: 0; display: inline-flex; diff --git a/frontend/src/utils/strategyEvaluationAiVerify.ts b/frontend/src/utils/strategyEvaluationAiVerify.ts index e249c2a..f6078eb 100644 --- a/frontend/src/utils/strategyEvaluationAiVerify.ts +++ b/frontend/src/utils/strategyEvaluationAiVerify.ts @@ -379,6 +379,58 @@ export function summarizeVerifyContext(ctx: StrategyEvaluationAiVerifyContext): ].join(' · '); } +/** AI 분석 탭 — 차트에서 선택한 봉 정보 (분석 실행 전·후 공통) */ +export function buildAiPanelSelectionSummary(opts: { + market: string; + timeframe: string; + bars: OHLCVBar[]; + selectedBarIndex: number; + snapshot?: VirtualIndicatorSnapshot; + barSignalHighlight: BarSignalHighlight; + strategyName?: string | null; +}): string | null { + const bar = opts.bars[opts.selectedBarIndex]; + if (!bar) return null; + + const timeLabel = new Date(bar.time * 1000).toLocaleString('ko-KR'); + const lines: string[] = []; + + if (opts.strategyName) { + lines.push(`전략: ${opts.strategyName}`); + } + lines.push(`${opts.market} · ${opts.timeframe}`); + lines.push(`선택 봉 index=${opts.selectedBarIndex} · ${timeLabel}`); + lines.push(`O=${bar.open} H=${bar.high} L=${bar.low} C=${bar.close}`); + + const { snapshot } = opts; + if (snapshot) { + const evalParts: string[] = []; + if (snapshot.overallEntryMet != null) { + evalParts.push(`매수 ${snapshot.overallEntryMet ? '충족' : '미충족'}`); + } + if (snapshot.overallExitMet != null) { + evalParts.push(`매도 ${snapshot.overallExitMet ? '충족' : '미충족'}`); + } + if (snapshot.matchRate != null) { + evalParts.push(`일치율 ${snapshot.matchRate}%`); + } + if (evalParts.length > 0) { + lines.push(evalParts.join(' · ')); + } + } else { + lines.push('봉 평가 준비 중…'); + } + + const { buy, sell } = opts.barSignalHighlight; + if (buy || sell) { + lines.push(`차트 시그널: ${[buy && 'BUY', sell && 'SELL'].filter(Boolean).join(', ')}`); + } else { + lines.push('선택 봉 차트 시그널 없음'); + } + + return lines.join('\n'); +} + /** Cursor AI에 붙여넣을 수정 포인트 항목 */ export interface AiVerifyFixPoint { id: string;