실시간 차트 백테스팅 결과 인쇄 기능 적용
This commit is contained in:
+37
-12
@@ -111,7 +111,9 @@ import {
|
|||||||
import { NotifyTopMenuBar, ChartToolbarNotify } from './components/NotifyUiBindings';
|
import { NotifyTopMenuBar, ChartToolbarNotify } from './components/NotifyUiBindings';
|
||||||
import { AppNotificationLayer } from './components/AppNotificationLayer';
|
import { AppNotificationLayer } from './components/AppNotificationLayer';
|
||||||
import BacktestSettingsModal from './components/BacktestSettingsModal';
|
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 DashboardPage = lazy(() => import('./components/DashboardPage'));
|
||||||
const StrategyPage = lazy(() => import('./components/StrategyPage'));
|
const StrategyPage = lazy(() => import('./components/StrategyPage'));
|
||||||
@@ -760,6 +762,7 @@ function App() {
|
|||||||
const [showBtSettings, setShowBtSettings] = useState(false);
|
const [showBtSettings, setShowBtSettings] = useState(false);
|
||||||
const [showBtResult, setShowBtResult] = useState(false);
|
const [showBtResult, setShowBtResult] = useState(false);
|
||||||
const [btStrategyName, setBtStrategyName] = useState('전략');
|
const [btStrategyName, setBtStrategyName] = useState('전략');
|
||||||
|
const [btReportCreatedAt, setBtReportCreatedAt] = useState<string | undefined>();
|
||||||
|
|
||||||
/** 현재 차트와 일치하는 백테스팅 결과만 UI·마커에 반영 */
|
/** 현재 차트와 일치하는 백테스팅 결과만 UI·마커에 반영 */
|
||||||
const btMatchesChart = useMemo(
|
const btMatchesChart = useMemo(
|
||||||
@@ -1228,6 +1231,34 @@ function App() {
|
|||||||
const barsMarket = useUpbit ? upbitBarsMarket : symbol;
|
const barsMarket = useUpbit ? upbitBarsMarket : symbol;
|
||||||
barsMarketRef.current = barsMarket;
|
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 전에 실행) */
|
/** 종목·타임프레임 전환 시 이전 틱 버퍼·실시간 허용 플래그 초기화 (paint 전에 실행) */
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
chartLiveReadyRef.current = false;
|
chartLiveReadyRef.current = false;
|
||||||
@@ -2315,6 +2346,7 @@ function App() {
|
|||||||
};
|
};
|
||||||
btMarkerPayloadRef.current = payload;
|
btMarkerPayloadRef.current = payload;
|
||||||
applyBacktestMarkersWhenReady(payload);
|
applyBacktestMarkersWhenReady(payload);
|
||||||
|
setBtReportCreatedAt(fmtListTimestamp(new Date().toISOString()));
|
||||||
}
|
}
|
||||||
if (appDefaults.btAutoPopup) setShowBtResult(true);
|
if (appDefaults.btAutoPopup) setShowBtResult(true);
|
||||||
}
|
}
|
||||||
@@ -2677,19 +2709,12 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 백테스팅 결과 모달 */}
|
{/* 백테스팅 결과 — 투자분석 상세 레포트 (인쇄·PDF) */}
|
||||||
{showBtResult && (
|
<BacktestAnalysisReportModal
|
||||||
<BacktestResultModal
|
open={showBtResult && chartBtReportModel != null}
|
||||||
analysis={btAnalysis ?? null}
|
|
||||||
stats={btStats ?? null}
|
|
||||||
signals={btSignals}
|
|
||||||
strategyName={btStrategyName}
|
|
||||||
symbol={symbol}
|
|
||||||
timeframe={timeframe}
|
|
||||||
barCount={bars.length}
|
|
||||||
onClose={() => setShowBtResult(false)}
|
onClose={() => setShowBtResult(false)}
|
||||||
|
model={chartBtReportModel}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 시리즈 클릭/호버 컨텍스트 툴바 */}
|
{/* 시리즈 클릭/호버 컨텍스트 툴바 */}
|
||||||
{ctxToolbar && (() => {
|
{ctxToolbar && (() => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type {
|
|||||||
BacktestAnalysis,
|
BacktestAnalysis,
|
||||||
BacktestResultRecord,
|
BacktestResultRecord,
|
||||||
BacktestSignal,
|
BacktestSignal,
|
||||||
|
BacktestStats,
|
||||||
} from './backendApi';
|
} from './backendApi';
|
||||||
import type { BacktestAnalysisReportModel } from '../components/backtest/BacktestAnalysisReportModal';
|
import type { BacktestAnalysisReportModel } from '../components/backtest/BacktestAnalysisReportModal';
|
||||||
import type { LiveExecutionItem } from './liveExecutionGroups';
|
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(
|
export function buildBacktestReportModel(
|
||||||
record: BacktestResultRecord,
|
record: BacktestResultRecord,
|
||||||
analysis: BacktestAnalysis,
|
analysis: BacktestAnalysis,
|
||||||
|
|||||||
Reference in New Issue
Block a user