ai 분석 수정
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user