ai 분석 수정

This commit is contained in:
Macbook
2026-06-17 17:25:54 +09:00
parent 9be08c1a50
commit 1698dd9c8e
4 changed files with 85 additions and 14 deletions
@@ -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<string | null>(null);
const [aiVerifyResult, setAiVerifyResult] = useState<StrategyEvaluationAiVerifyResponse | null>(null);
const [aiVerifyContext, setAiVerifyContext] = useState<import('../utils/strategyEvaluationAiVerify').StrategyEvaluationAiVerifyContext | null>(null);
const [aiVerifySummary, setAiVerifySummary] = useState<string | null>(null);
const aiVerifyGenRef = useRef(0);
const rightTabRef = useRef<RightTab>('signal');
const aiVerifyPollAbortRef = useRef<AbortController | null>(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) {
<StrategyEvaluationAiVerifyPanel
loading={aiVerifyRunning}
error={aiVerifyError}
contextSummary={aiVerifySummary}
selectionSummary={aiPanelSelectionSummary}
verifyContext={aiVerifyContext}
result={aiVerifyResult}
runDisabled={aiVerifyDisabled}
@@ -207,7 +207,8 @@ const FixPointsPanel: React.FC<{ bundle: AiVerifyFixPointsBundle }> = ({ 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<StrategyEvaluationAiVerifyPanelProps> = ({
loading,
error,
contextSummary,
selectionSummary,
verifyContext,
result,
runDisabled = false,
@@ -238,18 +239,15 @@ const StrategyEvaluationAiVerifyPanel: React.FC<StrategyEvaluationAiVerifyPanelP
}
}, [result?.analysis]);
const hasContent = loading || error || result;
return (
<div className="seval-ai-verify-panel">
<header className="seval-ai-verify-panel-head">
<div className="seval-ai-verify-panel-head-main">
{contextSummary && (
<p className="seval-ai-verify-summary">{contextSummary}</p>
)}
{!contextSummary && !hasContent && (
{selectionSummary ? (
<pre className="seval-ai-verify-panel-selection">{selectionSummary}</pre>
) : (
<p className="seval-ai-verify-panel-empty-hint">
AI LLM .
.
</p>
)}
</div>
@@ -265,7 +263,7 @@ const StrategyEvaluationAiVerifyPanel: React.FC<StrategyEvaluationAiVerifyPanelP
</>
) : (
result ? '다시 분석' : '분석 실행'
'AI 분석'
)}
</button>
</header>
@@ -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;
@@ -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;