diff --git a/frontend/src/pages/Backtest.tsx b/frontend/src/pages/Backtest.tsx index e9b6ac5..a855907 100644 --- a/frontend/src/pages/Backtest.tsx +++ b/frontend/src/pages/Backtest.tsx @@ -1,8 +1,8 @@ import { useState } from 'react' -import { useMutation } from '@tanstack/react-query' +import { useMutation, useQuery } from '@tanstack/react-query' import api from '../lib/api' import HtsPage from '../components/HtsPage' -import { Download, Play, TrendingUp, Database, BarChart3, AlertCircle } from 'lucide-react' +import { Download, Play, TrendingUp, Database, BarChart3, AlertCircle, Globe } from 'lucide-react' import { Line } from 'react-chartjs-2' import { Chart as ChartJS, @@ -31,7 +31,7 @@ interface BacktestResult { // ── ML 학습 데이터 생성 탭 ─────────────────────────────────────────────── function MLTrainingTab() { - const [stockCodes, setStockCodes] = useState('005930,000660,035720') // 삼성전자, SK하이닉스, 카카오 + const [stockCodes, setStockCodes] = useState('005930,000660,035720') const [startDate, setStartDate] = useState('2023-01-01') const [endDate, setEndDate] = useState('2025-12-31') const [forwardDays, setForwardDays] = useState(5) @@ -365,10 +365,198 @@ function StrategyTestTab() { ) } +// ── 전체 종목 백테스트 탭 ───────────────────────────────────────────────────────── + +function UniverseBacktestTab() { + const [universePreset, setUniversePreset] = useState('large_cap') + const [strategyPreset, setStrategyPreset] = useState('balanced') + const [startDate, setStartDate] = useState('2024-01-01') + const [endDate, setEndDate] = useState('2024-12-31') + const [topN, setTopN] = useState(20) + const [result, setResult] = useState(null) + + // 유니버스 프리셋 목록 조회 + const { data: universePresets } = useQuery({ + queryKey: ['universe-presets'], + queryFn: async () => (await api.get('/backtest/universe-presets')).data, + }) + + const runMutation = useMutation({ + mutationFn: async () => { + const res = await api.post('/backtest/run-universe', { + universe_preset: universePreset, + strategy_preset: strategyPreset, + start_date: startDate, + end_date: endDate, + top_n_results: topN, + }) + return res.data + }, + onSuccess: (data) => { + setResult(data) + }, + }) + + return ( +
+ {/* 설정 */} +
+

+ + 전체 종목 백테스트 +

+
+
+ + +
+
+ + +
+
+ +
+
+ + setStartDate(e.target.value)} + className="w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded text-white text-sm" + /> +
+
+ + setEndDate(e.target.value)} + className="w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded text-white text-sm" + /> +
+
+ + setTopN(Number(e.target.value))} + className="w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded text-white text-sm" + /> +
+
+ + +
+ + {/* 결과 */} + {result && ( + <> +
+

테스트 개요

+
+
+

테스트 종목 수

+

{result.total_tested}개

+
+
+

평균 수익률

+

0 ? 'text-green-400' : 'text-red-400' + }`}> + {(result.average_return * 100).toFixed(2)}% +

+
+
+

유니버스

+

{result.universe_preset}

+
+
+

전략

+

{result.strategy_preset}

+
+
+
+ + {/* Top N 결과 */} +
+

수익률 상위 {topN}종목

+
+ + + + + + + + + + + + + + {result.top_performers?.map((item: any, idx: number) => ( + + + + + + + + + + ))} + +
순위종목코드수익률MDD승률Sharpe거래수
{idx + 1}{item.stock_code} 0 ? 'text-green-400' : 'text-red-400' + }`}> + {(item.total_return_pct * 100).toFixed(2)}% + + {(item.mdd_pct * 100).toFixed(2)}% + + {(item.win_rate * 100).toFixed(1)}% + + {item.sharpe_ratio.toFixed(2)} + + {item.total_trades} +
+
+
+ + )} +
+ ) +} + // ── 메인 컴포넌트 ──────────────────────────────────────────────────────────────── const Backtest = () => { - const [activeTab, setActiveTab] = useState<'ml' | 'strategy'>('ml') + const [activeTab, setActiveTab] = useState<'ml' | 'strategy' | 'universe'>('ml') return ( @@ -392,7 +580,7 @@ const Backtest = () => { }`} > - ML 학습 데이터 생성 + ML 학습 데이터 + {/* 컨텐츠 */} - {activeTab === 'ml' ? : } + {activeTab === 'ml' && } + {activeTab === 'strategy' && } + {activeTab === 'universe' && } )