/** * BacktestHistoryPage * ───────────────────────────────────────────────────── * [좌측] 체크박스 선택 + 삭제 가능한 이력 목록 * [우측] 카드 기반 대시보드 결과 뷰 */ import React, { useEffect, useState, useCallback } from 'react'; import { loadBacktestResults, deleteBacktestResult, type BacktestResultRecord, type BacktestAnalysis, type BacktestSignal, } from '../utils/backendApi'; import { BacktestDashboard } from './BacktestResultModal'; // ── 유틸 ────────────────────────────────────────────────────────────────── const pct = (v: number) => isFinite(v) && v !== 0 ? `${v >= 0 ? '+' : ''}${(v * 100).toFixed(1)}%` : '–'; const fmtDateTime = (iso: string) => { const d = new Date(iso); return { date: `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`, time: `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`, }; }; // ── 아이콘 ──────────────────────────────────────────────────────────────── const IcTrash = () => ( ); const IcTrashAll = () => ( ); const IcRefresh = () => ( ); // ── 목록 아이템 ──────────────────────────────────────────────────────────── interface ListItemProps { r: BacktestResultRecord; checked: boolean; active: boolean; onToggle: () => void; onClick: () => void; } function ListItem({ r, checked, active, onToggle, onClick }: ListItemProps) { const ret = r.totalReturn ?? 0; const dt = fmtDateTime(r.createdAt); return (
{ e.stopPropagation(); onToggle(); }}>
{checked && }
{r.strategyName || '전략 없음'} 0 ? 'bh-pos' : ret < 0 ? 'bh-neg' : ''}`}>{pct(ret)}
{r.symbol} · {r.timeframe} · {r.totalTrades}회
{dt.date} {dt.time} {(r.winRate ?? 0) > 0 && ( 승률 {((r.winRate ?? 0) * 100).toFixed(0)}% )}
); } // ── 메인 페이지 ─────────────────────────────────────────────────────────── export function BacktestHistoryPage() { const [records, setRecords] = useState([]); const [selected, setSelected] = useState(null); const [checked, setChecked] = useState>(new Set()); const [loading, setLoading] = useState(true); // ── 로드 ──────────────────────────────────────────────────────────────── const fetchList = useCallback(async () => { setLoading(true); const list = await loadBacktestResults(); setRecords(list); if (list.length > 0) setSelected(prev => prev ?? list[0]); setLoading(false); }, []); useEffect(() => { fetchList(); }, [fetchList]); // ── 체크박스 ────────────────────────────────────────────────────────── const toggleCheck = useCallback((id: number) => { setChecked(prev => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next; }); }, []); const toggleAll = useCallback(() => { setChecked(prev => prev.size === records.length ? new Set() : new Set(records.map(r => r.id)) ); }, [records]); const allChecked = records.length > 0 && checked.size === records.length; const someChecked = checked.size > 0 && !allChecked; // ── 삭제 ───────────────────────────────────────────────────────────── const deleteSelected = useCallback(async () => { if (checked.size === 0) return; if (!window.confirm(`선택한 ${checked.size}개 백테스팅 결과를 삭제하시겠습니까?`)) return; await Promise.all([...checked].map(id => deleteBacktestResult(id))); setRecords(prev => { const next = prev.filter(r => !checked.has(r.id)); if (selected && checked.has(selected.id)) setSelected(next[0] ?? null); return next; }); setChecked(new Set()); }, [checked, selected]); const deleteAll = useCallback(async () => { if (records.length === 0) return; if (!window.confirm(`전체 ${records.length}개 백테스팅 결과를 삭제하시겠습니까?`)) return; await Promise.all(records.map(r => deleteBacktestResult(r.id))); setRecords([]); setSelected(null); setChecked(new Set()); }, [records]); // ── 상세 데이터 파싱 ────────────────────────────────────────────────── const getDetail = (r: BacktestResultRecord) => { let analysis: BacktestAnalysis | null = null; let signals: BacktestSignal[] = []; try { if (r.analysisJson) analysis = JSON.parse(r.analysisJson); if (r.signalsJson) signals = JSON.parse(r.signalsJson); } catch (_) {} return { analysis, signals }; }; // ── 렌더 ───────────────────────────────────────────────────────────── return (
{/* ── 좌측 목록 ── */} {/* ── 우측 대시보드 ── */}
{selected ? ( (() => { const { analysis, signals } = getDetail(selected); return (
); })() ) : (
📈

좌측 목록에서 백테스팅 결과를 선택하세요.

)}
); } export default BacktestHistoryPage;