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 { import {
buildStrategyEvaluationAiVerifyContext, buildStrategyEvaluationAiVerifyContext,
requestStrategyEvaluationAiVerify, requestStrategyEvaluationAiVerify,
summarizeVerifyContext, buildAiPanelSelectionSummary,
} from '../utils/strategyEvaluationAiVerify'; } from '../utils/strategyEvaluationAiVerify';
import type { StrategyEvaluationAiVerifyResponse } from '../utils/backendApi'; 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 [aiVerifyError, setAiVerifyError] = useState<string | null>(null);
const [aiVerifyResult, setAiVerifyResult] = useState<StrategyEvaluationAiVerifyResponse | null>(null); const [aiVerifyResult, setAiVerifyResult] = useState<StrategyEvaluationAiVerifyResponse | null>(null);
const [aiVerifyContext, setAiVerifyContext] = useState<import('../utils/strategyEvaluationAiVerify').StrategyEvaluationAiVerifyContext | null>(null); const [aiVerifyContext, setAiVerifyContext] = useState<import('../utils/strategyEvaluationAiVerify').StrategyEvaluationAiVerifyContext | null>(null);
const [aiVerifySummary, setAiVerifySummary] = useState<string | null>(null);
const aiVerifyGenRef = useRef(0); const aiVerifyGenRef = useRef(0);
const rightTabRef = useRef<RightTab>('signal'); const rightTabRef = useRef<RightTab>('signal');
const aiVerifyPollAbortRef = useRef<AbortController | null>(null); const aiVerifyPollAbortRef = useRef<AbortController | null>(null);
@@ -298,6 +297,19 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
[backtestSignals, selectedBarTimeSec, selectedBarIndex], [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[]) => { const handleSignalsChange = useCallback((signals: BacktestSignal[]) => {
setBacktestSignals(signals); setBacktestSignals(signals);
}, []); }, []);
@@ -439,7 +451,6 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
indicatorParams, indicatorParams,
evaluationBarCount: Math.min(BACKTEST_DISPLAY_BAR_COUNT, bars.length), evaluationBarCount: Math.min(BACKTEST_DISPLAY_BAR_COUNT, bars.length),
}); });
setAiVerifySummary(summarizeVerifyContext(context));
setAiVerifyContext(context); setAiVerifyContext(context);
const result = await requestStrategyEvaluationAiVerify(context, { const result = await requestStrategyEvaluationAiVerify(context, {
@@ -863,7 +874,7 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
<StrategyEvaluationAiVerifyPanel <StrategyEvaluationAiVerifyPanel
loading={aiVerifyRunning} loading={aiVerifyRunning}
error={aiVerifyError} error={aiVerifyError}
contextSummary={aiVerifySummary} selectionSummary={aiPanelSelectionSummary}
verifyContext={aiVerifyContext} verifyContext={aiVerifyContext}
result={aiVerifyResult} result={aiVerifyResult}
runDisabled={aiVerifyDisabled} runDisabled={aiVerifyDisabled}
@@ -207,7 +207,8 @@ const FixPointsPanel: React.FC<{ bundle: AiVerifyFixPointsBundle }> = ({ bundle
export interface StrategyEvaluationAiVerifyPanelProps { export interface StrategyEvaluationAiVerifyPanelProps {
loading: boolean; loading: boolean;
error: string | null; error: string | null;
contextSummary: string | null; /** 차트 선택 봉·평가 요약 (실시간) */
selectionSummary: string | null;
verifyContext: StrategyEvaluationAiVerifyContext | null; verifyContext: StrategyEvaluationAiVerifyContext | null;
result: StrategyEvaluationAiVerifyResponse | null; result: StrategyEvaluationAiVerifyResponse | null;
runDisabled?: boolean; runDisabled?: boolean;
@@ -217,7 +218,7 @@ export interface StrategyEvaluationAiVerifyPanelProps {
const StrategyEvaluationAiVerifyPanel: React.FC<StrategyEvaluationAiVerifyPanelProps> = ({ const StrategyEvaluationAiVerifyPanel: React.FC<StrategyEvaluationAiVerifyPanelProps> = ({
loading, loading,
error, error,
contextSummary, selectionSummary,
verifyContext, verifyContext,
result, result,
runDisabled = false, runDisabled = false,
@@ -238,18 +239,15 @@ const StrategyEvaluationAiVerifyPanel: React.FC<StrategyEvaluationAiVerifyPanelP
} }
}, [result?.analysis]); }, [result?.analysis]);
const hasContent = loading || error || result;
return ( return (
<div className="seval-ai-verify-panel"> <div className="seval-ai-verify-panel">
<header className="seval-ai-verify-panel-head"> <header className="seval-ai-verify-panel-head">
<div className="seval-ai-verify-panel-head-main"> <div className="seval-ai-verify-panel-head-main">
{contextSummary && ( {selectionSummary ? (
<p className="seval-ai-verify-summary">{contextSummary}</p> <pre className="seval-ai-verify-panel-selection">{selectionSummary}</pre>
)} ) : (
{!contextSummary && !hasContent && (
<p className="seval-ai-verify-panel-empty-hint"> <p className="seval-ai-verify-panel-empty-hint">
AI LLM . .
</p> </p>
)} )}
</div> </div>
@@ -265,7 +263,7 @@ const StrategyEvaluationAiVerifyPanel: React.FC<StrategyEvaluationAiVerifyPanelP
</> </>
) : ( ) : (
result ? '다시 분석' : '분석 실행' 'AI 분석'
)} )}
</button> </button>
</header> </header>
@@ -1203,6 +1203,16 @@
color: var(--se-text-muted, #8b949e); 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 { .seval-ai-verify-panel-run-btn {
flex-shrink: 0; flex-shrink: 0;
display: inline-flex; display: inline-flex;
@@ -379,6 +379,58 @@ export function summarizeVerifyContext(ctx: StrategyEvaluationAiVerifyContext):
].join(' · '); ].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에 붙여넣을 수정 포인트 항목 */ /** Cursor AI에 붙여넣을 수정 포인트 항목 */
export interface AiVerifyFixPoint { export interface AiVerifyFixPoint {
id: string; id: string;