From e5a23f176dd91672d30e8aafe332ed959189c86c Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 9 Sep 2026 00:41:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20M8.6b=20=E5=9B=9E=E6=B5=8B?= =?UTF-8?q?=E9=A1=B5=E5=81=9A=E5=AE=9E=20+=20=E9=A6=96=E9=A1=B5=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=E6=9B=B4=E6=96=B0=EF=BC=88=E9=9B=B6=E6=96=B0=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=20API=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - backtest 页:成本参数表单(手续费/印花税/滑点 %)+ 初始资金 → ResearchSpec.costs/ initial_capital;结果新增年度收益卡与成交明细表(买/卖日、买/卖价、收益、累计换手) - Dashboard 入口卡加入「股票筛选」「交易信号」(6 张) - lib/types.ts:ResearchSpec 增加 price_adjustment/costs/portfolio/initial_capital, Trade 补 entry_price/exit_price - 页面 200 冒烟 + tsc --noEmit 通过 --- frontend/web/app/backtest/page.tsx | 78 ++++++++++++++++++++++++++++++ frontend/web/app/page.tsx | 14 ++++++ frontend/web/lib/types.ts | 11 ++++- 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/frontend/web/app/backtest/page.tsx b/frontend/web/app/backtest/page.tsx index 9c2e5ab..2893966 100644 --- a/frontend/web/app/backtest/page.tsx +++ b/frontend/web/app/backtest/page.tsx @@ -28,6 +28,10 @@ export default function BacktestPage() { const [start, setStart] = useState(""); const [end, setEnd] = useState(""); const [excludeSt, setExcludeSt] = useState(true); + const [commission, setCommission] = useState(0.03); // % + const [stamp, setStamp] = useState(0.05); + const [slippage, setSlippage] = useState(0.1); + const [capital, setCapital] = useState(1_000_000); const [result, setResult] = useState(null); const [running, setRunning] = useState(false); const [jobId, setJobId] = useState(""); @@ -65,6 +69,12 @@ export default function BacktestPage() { factors: [{ name: factor, weight: 1 }], selection: { top_n: topN }, rebalance, + costs: { + commission_rate: commission / 100, + stamp_tax_rate: stamp / 100, + slippage_rate: slippage / 100, + }, + initial_capital: capital, period: [start, end], }; // 异步 Job:后台执行(全市场可能数十秒),轮询到终态 @@ -133,6 +143,22 @@ export default function BacktestPage() { + + setCommission(Number(e.target.value))} /> + + + setStamp(Number(e.target.value))} /> + + + setSlippage(Number(e.target.value))} /> + + + setCapital(Number(e.target.value))} /> + setStart(e.target.value)} /> @@ -228,6 +254,58 @@ function ResultView({ result }: { result: BacktestResult }) { )} + {result.yearly_returns.length ? ( + 均值 {(result.yearly_returns.reduce((a, y) => a + y.return_pct, 0) / result.yearly_returns.length).toFixed(2)}%}> +
+ {result.yearly_returns.map((y) => ( + + {y.year} + = 0 ? "tone-pos" : "tone-neg"}> + {y.return_pct.toFixed(2)}% + + + ))} +
+
+ ) : null} + + {result.trades.length ? ( + {result.turnover_pct.toFixed(2)}% 累计换手} + > +
+ + + + + + + + + + + + + {result.trades.map((t) => ( + + + + + + + + + ))} + +
买入日卖出日代码买价卖价收益
{t.entry_date}{t.exit_date}{t.symbol}{t.entry_price?.toFixed(2) ?? "-"}{t.exit_price?.toFixed(2) ?? "-"}= 0 ? "tone-pos" : "tone-neg"}> + {t.return_pct.toFixed(2)}% +
+
+
+ ) : null} + ); diff --git a/frontend/web/app/page.tsx b/frontend/web/app/page.tsx index 5b3dd5d..0004b85 100644 --- a/frontend/web/app/page.tsx +++ b/frontend/web/app/page.tsx @@ -36,6 +36,20 @@ const LINKS = [ icon: "gauge" as const, tone: "quick-card--warn", }, + { + href: "/selection", + title: "股票筛选", + desc: "因子评分 TopN 或条件选股,支持当前/历史时点,结果可解释", + icon: "target" as const, + tone: "quick-card--violet", + }, + { + href: "/signals", + title: "交易信号", + desc: "按评分排名 + 趋势规则输出 BUY / WATCH / SELL 及理由", + icon: "scale" as const, + tone: "quick-card--pos", + }, { href: "/stocks", title: "股票池", diff --git a/frontend/web/lib/types.ts b/frontend/web/lib/types.ts index 6afba02..0856f0f 100644 --- a/frontend/web/lib/types.ts +++ b/frontend/web/lib/types.ts @@ -27,9 +27,18 @@ export interface ResearchSpec { min_listing_days?: number; symbols?: string[]; }; + price_adjustment?: "none" | "qfq"; factors: { name: string; weight: number }[]; selection: { top_n: number }; rebalance: "weekly" | "monthly"; + costs?: { + commission_rate?: number; + stamp_tax_rate?: number; + slippage_rate?: number; + benchmark?: string; + }; + portfolio?: { weighting?: string; max_position_pct?: number | null; max_industry_weight_pct?: number | null }; + initial_capital?: number; period: [string, string]; } @@ -60,7 +69,7 @@ export interface BacktestResult { monthly_returns: { year: number; month: number; return_pct: number }[]; yearly_returns: { year: number; return_pct: number }[]; positions: { date: string; symbol: string; weight: number }[]; - trades: { entry_date: string; exit_date: string; symbol: string; return_pct: number }[]; + trades: { entry_date: string; exit_date: string; symbol: string; entry_price?: number; exit_price?: number; return_pct: number }[]; turnover_pct: number; unimplemented: string[]; config_snapshot: Record;