"use client"; import { useEffect, useState } from "react"; import { apiGet, apiPost } from "@/lib/api"; import type { BacktestResult } from "@/lib/types"; interface ExperimentMeta { id: string; kind: string; factors: string[]; period?: [string, string] | null; rebalance?: string; top_n?: number | null; summary_text?: string | null; code_version?: string | null; created_at?: string; } interface ExperimentDetail extends ExperimentMeta { spec: { universe?: Record }; result: BacktestResult | null; } export default function ExperimentsPage() { const [exps, setExps] = useState([]); const [detail, setDetail] = useState(null); const [jobId, setJobId] = useState(""); const [jobStatus, setJobStatus] = useState(""); const [error, setError] = useState(""); function load() { apiGet("/experiments") .then(setExps) .catch((e: Error) => setError(e.message)); } useEffect(load, []); function open(id: string) { apiGet(`/experiments/${id}`) .then(setDetail) .catch((e: Error) => setError(e.message)); } function rerun(exp: ExperimentMeta) { setError(""); setJobId(""); setJobStatus("submitting"); apiPost<{ job_id: string; status: string }>(`/experiments/${exp.id}/rerun`, {}) .then((r) => { setJobId(r.job_id); setJobStatus(r.status); poll(r.job_id); }) .catch((e: Error) => setError(e.message)); } function poll(id: string) { let tries = 0; const timer = window.setInterval(() => { tries += 1; apiGet<{ status: string }>(`/jobs/${id}`) .then((j) => { setJobStatus(j.status); if (j.status === "success" || j.status === "failed" || tries > 60) { window.clearInterval(timer); load(); } }) .catch(() => window.clearInterval(timer)); }, 1000); } const s = detail?.result?.summary; return ( <>

实验归档(Experiment · 可复现)

{error &&
{error}
} {jobId && (
复跑 Job {jobId} → 状态:{jobStatus}
)}
{exps.map((e) => ( ))} {exps.length === 0 && ( )}
ID 类型 因子 区间 摘要 代码版本
{e.id} {e.kind} {e.factors.join(", ")} {e.period ? `${e.period[0]} ~ ${e.period[1]}` : "—"} {e.summary_text ?? "—"} {e.code_version ?? "—"}
暂无实验。先在「回测」或「因子研究」中运行一次,即会自动归档。
{detail && s && (

{detail.id} · {detail.factors.join(", ")} · {detail.period?.[0]}~{detail.period?.[1]}

总收益
{s.total_return_pct.toFixed(2)}%
年化
{s.annual_return_pct.toFixed(2)}%
Sharpe
{s.sharpe.toFixed(2)}
最大回撤
{s.max_drawdown_pct.toFixed(2)}%
spec(可复现输入):{JSON.stringify(detail.spec).slice(0, 400)}…
)} ); }