분석결과 레포트 수정

This commit is contained in:
Macbook
2026-06-19 07:19:32 +09:00
parent 8bceadc55b
commit 2514e0adae
7 changed files with 565 additions and 8 deletions
@@ -4,10 +4,11 @@
* BacktestDashboard — 카드 기반 대시보드 (재사용 가능)
* BacktestResultModal — 드래그 팝업으로 Dashboard를 감싸는 래퍼
*/
import React from 'react';
import React, { useMemo, useState } from 'react';
import type { BacktestAnalysis, BacktestSignal, BacktestStats } from '../utils/backendApi';
import { useDraggablePanel } from '../hooks/useDraggablePanel';
import { repairUtf8Mojibake } from '../utils/textEncoding';
import type { EvaluationAllocationReport } from './backtest/BacktestAnalysisReportModal';
import {
pct,
pctAbs,
@@ -58,6 +59,9 @@ export interface BacktestDashboardProps {
reportMode?: boolean;
/** false — 거래 시그널 섹션 숨김 (별도 패널에 표시) */
hideSignals?: boolean;
/** 전략평가 — 일괄/분할 매수 방식별 분석 */
evaluationAllocations?: EvaluationAllocationReport[];
reportSignalFilterNote?: string;
}
// ── 시그널 타입 ───────────────────────────────────────────────────────────
@@ -70,8 +74,17 @@ export function BacktestDashboard({
analysis, signals, strategyName, symbol, timeframe, barCount, createdAt,
reportMode = false,
hideSignals = false,
evaluationAllocations,
reportSignalFilterNote,
}: BacktestDashboardProps) {
const a = analysis;
const [allocMode, setAllocMode] = useState<'full' | 'split'>('split');
const displayAnalysis = useMemo(() => {
if (!evaluationAllocations?.length) return analysis;
return evaluationAllocations.find(e => e.mode === allocMode)?.analysis ?? analysis;
}, [analysis, evaluationAllocations, allocMode]);
const a = displayAnalysis;
if (!a) {
return (
<div className="brd-empty-state">
@@ -120,6 +133,49 @@ export function BacktestDashboard({
</div>
</div>
{evaluationAllocations && evaluationAllocations.length >= 2 && (
<div className="brd-row">
<div className="brd-card brd-card--full">
<SectionTitle icon="📋" title="매수 방식별 수익률 비교" />
{reportSignalFilterNote && (
<p className="brd-eval-filter-note">{reportSignalFilterNote}</p>
)}
<div className="brd-alloc-tabs">
{evaluationAllocations.map(item => (
<button
key={item.mode}
type="button"
className={`brd-alloc-tab${allocMode === item.mode ? ' brd-alloc-tab--active' : ''}`}
onClick={() => setAllocMode(item.mode)}
>
{item.label}
</button>
))}
</div>
<div className="brd-alloc-compare">
{evaluationAllocations.map(item => (
<div
key={item.mode}
className={`brd-alloc-card${allocMode === item.mode ? ' brd-alloc-card--active' : ''}`}
role="button"
tabIndex={0}
onClick={() => setAllocMode(item.mode)}
onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') setAllocMode(item.mode); }}
>
<div className="brd-alloc-card-label">{item.label}</div>
<div className={`brd-alloc-card-return ${colorCls(item.analysis.totalReturnPct)}`}>
{pct(item.analysis.totalReturnPct)}
</div>
<div className="brd-alloc-card-sub">
{wonFmt(item.analysis.finalEquity)} · {pctAbs(item.analysis.winRate)}
</div>
</div>
))}
</div>
</div>
</div>
)}
{/* ── Row 1: KPI 카드 6개 ── */}
<div className="brd-row">
<div className="brd-kpi-grid">
@@ -48,7 +48,7 @@ import { resolveBarSignalHighlight } from '../utils/strategyEvaluationBarSignals
import type { OHLCVBar } from '../types';
import BacktestAnalysisReportModal from './backtest/BacktestAnalysisReportModal';
import type { BacktestAnalysisReportModel } from './backtest/BacktestAnalysisReportModal';
import { buildChartBacktestReportModel } from '../utils/backtestReportModel';
import { buildStrategyEvaluationReportModel } from '../utils/strategyEvaluationReport';
import { BACKTEST_DISPLAY_BAR_COUNT } from '../utils/backtestWarmup';
import StrategyEvaluationAiVerifyPanel from './strategyEvaluation/StrategyEvaluationAiVerifyPanel';
import StrategyEvaluationChartSettingsTab from './strategyEvaluation/StrategyEvaluationChartSettingsTab';
@@ -604,7 +604,7 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
timeframe: chartTimeframe,
symbol: market,
strategyName: selectedStrategy.name,
settings: btSettings,
settings: { ...btSettings, positionMode: 'SIGNAL_ONLY' },
indicatorParams,
evaluationBarCount,
});
@@ -614,14 +614,16 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
return;
}
const model = buildChartBacktestReportModel({
analysis: res.analysis ?? null,
stats: res.stats ?? null,
const initialCapital = btSettings.initialCapital ?? res.analysis?.initialCapital ?? 10_000_000;
const model = buildStrategyEvaluationReportModel({
signals: res.signals,
initialCapital,
strategyName: selectedStrategy.name ?? strategyLabel,
symbol: market.replace(/^KRW-/, ''),
timeframe: chartTimeframe,
barCount: bars.length,
firstBarClose: bars[0]?.close,
lastBarClose: bars[bars.length - 1]?.close,
});
if (!model) {
window.alert('분석 데이터를 생성할 수 없습니다.');
@@ -8,6 +8,14 @@ import { BacktestDashboard } from '../BacktestResultModal';
import { printReportDocument } from '../../utils/printReportDocument';
import '../../styles/backtestReportPrint.css';
export type EvaluationAllocationMode = 'full' | 'split';
export interface EvaluationAllocationReport {
mode: EvaluationAllocationMode;
label: string;
analysis: BacktestAnalysis;
}
export interface BacktestAnalysisReportModel {
analysis: BacktestAnalysis;
signals: BacktestSignal[];
@@ -18,6 +26,9 @@ export interface BacktestAnalysisReportModel {
barCount: number;
createdAt?: string;
reportKind: 'backtest' | 'live';
/** 전략평가 — 일괄/분할 매수 방식별 분석 */
evaluationAllocations?: EvaluationAllocationReport[];
reportSignalFilterNote?: string;
}
interface Props {
@@ -135,6 +146,8 @@ export default function BacktestAnalysisReportModal({ open, onClose, model }: Pr
barCount={model.barCount}
createdAt={model.createdAt}
reportMode
evaluationAllocations={model.evaluationAllocations}
reportSignalFilterNote={model.reportSignalFilterNote}
/>
<footer className="btd-report-print-footer">
<p> GoldenChart · .</p>