실시간 차트 백테스팅 결과 인쇄 기능 적용
This commit is contained in:
+37
-12
@@ -111,7 +111,9 @@ import {
|
||||
import { NotifyTopMenuBar, ChartToolbarNotify } from './components/NotifyUiBindings';
|
||||
import { AppNotificationLayer } from './components/AppNotificationLayer';
|
||||
import BacktestSettingsModal from './components/BacktestSettingsModal';
|
||||
import { BacktestResultModal } from './components/BacktestResultModal';
|
||||
import BacktestAnalysisReportModal from './components/backtest/BacktestAnalysisReportModal';
|
||||
import { buildChartBacktestReportModel } from './utils/backtestReportModel';
|
||||
import { fmtListTimestamp } from './utils/backtestUiUtils';
|
||||
|
||||
const DashboardPage = lazy(() => import('./components/DashboardPage'));
|
||||
const StrategyPage = lazy(() => import('./components/StrategyPage'));
|
||||
@@ -760,6 +762,7 @@ function App() {
|
||||
const [showBtSettings, setShowBtSettings] = useState(false);
|
||||
const [showBtResult, setShowBtResult] = useState(false);
|
||||
const [btStrategyName, setBtStrategyName] = useState('전략');
|
||||
const [btReportCreatedAt, setBtReportCreatedAt] = useState<string | undefined>();
|
||||
|
||||
/** 현재 차트와 일치하는 백테스팅 결과만 UI·마커에 반영 */
|
||||
const btMatchesChart = useMemo(
|
||||
@@ -1228,6 +1231,34 @@ function App() {
|
||||
const barsMarket = useUpbit ? upbitBarsMarket : symbol;
|
||||
barsMarketRef.current = barsMarket;
|
||||
|
||||
const chartBtReportModel = useMemo(() => {
|
||||
if (!btMatchesChart) return null;
|
||||
const reportSymbol = btSymbol ?? symbol;
|
||||
const reportTf = btTimeframe ?? timeframe;
|
||||
return buildChartBacktestReportModel({
|
||||
analysis: btAnalysis,
|
||||
stats: btStats,
|
||||
signals: btSignals,
|
||||
strategyName: btStrategyName,
|
||||
symbol: reportSymbol,
|
||||
timeframe: reportTf,
|
||||
barCount: bars.length,
|
||||
createdAt: btReportCreatedAt,
|
||||
});
|
||||
}, [
|
||||
btMatchesChart,
|
||||
btAnalysis,
|
||||
btStats,
|
||||
btSignals,
|
||||
btStrategyName,
|
||||
btSymbol,
|
||||
btTimeframe,
|
||||
symbol,
|
||||
timeframe,
|
||||
bars.length,
|
||||
btReportCreatedAt,
|
||||
]);
|
||||
|
||||
/** 종목·타임프레임 전환 시 이전 틱 버퍼·실시간 허용 플래그 초기화 (paint 전에 실행) */
|
||||
useLayoutEffect(() => {
|
||||
chartLiveReadyRef.current = false;
|
||||
@@ -2315,6 +2346,7 @@ function App() {
|
||||
};
|
||||
btMarkerPayloadRef.current = payload;
|
||||
applyBacktestMarkersWhenReady(payload);
|
||||
setBtReportCreatedAt(fmtListTimestamp(new Date().toISOString()));
|
||||
}
|
||||
if (appDefaults.btAutoPopup) setShowBtResult(true);
|
||||
}
|
||||
@@ -2677,19 +2709,12 @@ function App() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 백테스팅 결과 모달 */}
|
||||
{showBtResult && (
|
||||
<BacktestResultModal
|
||||
analysis={btAnalysis ?? null}
|
||||
stats={btStats ?? null}
|
||||
signals={btSignals}
|
||||
strategyName={btStrategyName}
|
||||
symbol={symbol}
|
||||
timeframe={timeframe}
|
||||
barCount={bars.length}
|
||||
{/* 백테스팅 결과 — 투자분석 상세 레포트 (인쇄·PDF) */}
|
||||
<BacktestAnalysisReportModal
|
||||
open={showBtResult && chartBtReportModel != null}
|
||||
onClose={() => setShowBtResult(false)}
|
||||
model={chartBtReportModel}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 시리즈 클릭/호버 컨텍스트 툴바 */}
|
||||
{ctxToolbar && (() => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
BacktestAnalysis,
|
||||
BacktestResultRecord,
|
||||
BacktestSignal,
|
||||
BacktestStats,
|
||||
} from './backendApi';
|
||||
import type { BacktestAnalysisReportModel } from '../components/backtest/BacktestAnalysisReportModal';
|
||||
import type { LiveExecutionItem } from './liveExecutionGroups';
|
||||
@@ -42,6 +43,60 @@ export function buildAnalysisFromLive(
|
||||
};
|
||||
}
|
||||
|
||||
function buildAnalysisFromStats(s: BacktestStats): BacktestAnalysis {
|
||||
return {
|
||||
initialCapital: 10_000_000,
|
||||
finalEquity: s.finalEquity ?? 0,
|
||||
totalReturnPct: s.totalReturn ?? 0,
|
||||
totalProfitLoss: (s.finalEquity ?? 0) - 10_000_000,
|
||||
grossProfit: 0,
|
||||
grossLoss: 0,
|
||||
avgReturnPct: s.avgReturn ?? 0,
|
||||
profitLossRatio: 0,
|
||||
numberOfPositions: s.totalTrades ?? 0,
|
||||
numberOfWinning: s.winTrades ?? 0,
|
||||
numberOfLosing: (s.totalTrades ?? 0) - (s.winTrades ?? 0),
|
||||
numberOfBreakEven: 0,
|
||||
winRate: s.winRate ?? 0,
|
||||
maxDrawdownPct: s.maxDrawdown ?? 0,
|
||||
maxRunupPct: 0,
|
||||
sharpeRatio: 0,
|
||||
sortinoRatio: 0,
|
||||
calmarRatio: 0,
|
||||
valueAtRisk95: 0,
|
||||
expectedShortfall: 0,
|
||||
buyAndHoldReturnPct: 0,
|
||||
vsBuyAndHold: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** 실시간 차트에서 실행한 백테스트 → 투자분석 상세 레포트 모델 */
|
||||
export function buildChartBacktestReportModel(opts: {
|
||||
analysis: BacktestAnalysis | null;
|
||||
stats: BacktestStats | null;
|
||||
signals: BacktestSignal[];
|
||||
strategyName: string;
|
||||
symbol: string;
|
||||
timeframe: string;
|
||||
barCount: number;
|
||||
createdAt?: string;
|
||||
}): BacktestAnalysisReportModel | null {
|
||||
const analysis = opts.analysis ?? (opts.stats ? buildAnalysisFromStats(opts.stats) : null);
|
||||
if (!analysis) return null;
|
||||
const market = toUpbitMarket(opts.symbol);
|
||||
return {
|
||||
analysis,
|
||||
signals: opts.signals,
|
||||
strategyName: repairUtf8Mojibake(opts.strategyName || '전략'),
|
||||
symbol: opts.symbol,
|
||||
symbolKo: repairUtf8Mojibake(getKoreanName(market)),
|
||||
timeframe: opts.timeframe,
|
||||
barCount: opts.barCount || 300,
|
||||
createdAt: opts.createdAt,
|
||||
reportKind: 'backtest',
|
||||
};
|
||||
}
|
||||
|
||||
export function buildBacktestReportModel(
|
||||
record: BacktestResultRecord,
|
||||
analysis: BacktestAnalysis,
|
||||
|
||||
Reference in New Issue
Block a user