/**
* 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(); }}>
{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;