fix: unify backtest execution and add forward validation candidates
Share point-in-time signals and a cash-funded order ledger across backtests and trading. Correct eligibility, missing-price fills, quality factors, regime inputs, warmup windows, and cash-injection accounting. Add reproducible registered-strategy comparisons and paired forward candidates with a 20% resize band. Validation: 125 Python tests, 23 web tests, TypeScript/Vite build, 87 strategy causality checks, and real-data target/order/equity parity for 10 strategies.
This commit is contained in:
@@ -261,9 +261,12 @@ export function LivePage() {
|
||||
function emptyHint(book: LiveBook | undefined, market: StrategyMarket): string {
|
||||
if (!book?.startDate) return "暂无在线策略。运行 quant 的 trader.py auto 后再刷新。";
|
||||
const currency = currencyForMarket(market);
|
||||
const fee = book.feePerOrder ? ` · 每笔 ${formatMoney(book.feePerOrder[market], currency)}` : "";
|
||||
const schedule = book.feeSchedule?.[market];
|
||||
const fee = schedule
|
||||
? ` · 佣金最低 ${formatMoney(schedule.fixed_fee, currency)},比例成本 ${(schedule.tx_cost * 100).toFixed(2)}%`
|
||||
: book.feePerOrder ? ` · 每单佣金 ${formatMoney(book.feePerOrder[market], currency)}` : "";
|
||||
return `${book.label}:${book.strategies[market].length} 个策略,各 ${formatMoney(book.capital[market], currency)}${fee},`
|
||||
+ `${book.startDate[market]} 收盘后出现首日记录。`;
|
||||
+ `${book.startDate[market]} 起首个交易日收盘后出现记录。`;
|
||||
}
|
||||
|
||||
function LiveStrategyDetail({ strategy }: { strategy: LiveStrategyState }) {
|
||||
|
||||
@@ -594,6 +594,15 @@ function EquityCurveChart({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{result.meta.warnings?.length ? (
|
||||
<details className="backtestAssumptions">
|
||||
<summary>回测数据与成交假设</summary>
|
||||
<ul>{result.meta.warnings.map((message) => <li key={message}>{message}</li>)}</ul>
|
||||
</details>
|
||||
) : null}
|
||||
{result.meta.errors?.length ? (
|
||||
<p role="alert">未完成回测:{result.meta.errors.map((e) => `${e.strategy}: ${e.error}`).join(";")}</p>
|
||||
) : null}
|
||||
<div className="legendRow">
|
||||
{result.strategies.length === 0 && (
|
||||
<span className="legendEmpty">从左侧选取策略加入对比</span>
|
||||
|
||||
@@ -17,7 +17,7 @@ function ensureCacheDir(): void {
|
||||
}
|
||||
|
||||
function configHash(config: BacktestConfig): string {
|
||||
const serialized = stableStringify(config);
|
||||
const serialized = stableStringify({ engine: "cash-ledger-pit-v2", config });
|
||||
return createHash("md5").update(serialized).digest("hex").slice(0, 12);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ export interface InjectionEvent {
|
||||
amount: number;
|
||||
}
|
||||
|
||||
// Backtest metrics are time-weighted (computed on the no-injection curve).
|
||||
// Backtest metrics use unit NAV after executing contributions and their fees.
|
||||
// The bridge serializes NaN as null, so every field may be null.
|
||||
export interface StrategyMetrics {
|
||||
totalReturn: number | null;
|
||||
@@ -107,7 +107,7 @@ export interface StrategyResult {
|
||||
label: string;
|
||||
// Money curve (includes injections when a schedule exists).
|
||||
equityCurve: EquityPoint[];
|
||||
// Same strategy without injections; metrics are computed on this curve.
|
||||
// Same strategy without injections, supplied for comparison.
|
||||
baseEquityCurve: EquityPoint[] | null;
|
||||
metrics: StrategyMetrics;
|
||||
capitalFlows?: CapitalFlows;
|
||||
@@ -121,6 +121,11 @@ export interface StrategyResult {
|
||||
}
|
||||
|
||||
export interface BacktestMeta {
|
||||
requestedYears?: number;
|
||||
engineVersion?: string;
|
||||
warnings?: string[];
|
||||
coverage?: { minimum: number; median: number } | null;
|
||||
errors?: { strategy: string; error: string }[];
|
||||
market: StrategyMarket;
|
||||
years: number;
|
||||
initialCapital: number;
|
||||
@@ -163,6 +168,9 @@ export interface TradeLogEntry {
|
||||
// A live book: a cohort of strategies, each with its own account, started on
|
||||
// one date with one capital and fee schedule. id "" is the original book.
|
||||
export interface LiveBook {
|
||||
feeSchedule?: Partial<Record<StrategyMarket, {
|
||||
tx_cost: number; fixed_fee: number; fee_base: number; fee_per_share: number;
|
||||
}>> | null;
|
||||
id: string;
|
||||
label: string;
|
||||
startDate: Record<StrategyMarket, string> | null;
|
||||
|
||||
Reference in New Issue
Block a user