feat: 量化引擎加固 — 新增测试 + 数据/因子/回测层优化

- 新增 finance/tests/ 6 个测试套件(agents/backtest/dao_upsert/factors/features/fundamental_lookahead)
- 数据层: data_manager / dao 优化,新增 upsert 逻辑
- 因子层: 基本面因子抽象定位 _mapping、ROE/PE/PB 重构
- 回测层: vectorbt/engine 大改动(251 行),report 增强
- ML 层: features/backtest_integration 特征工程与回测优化
- CLI: agent_cli 重构
- config/settings 扩充配置项
This commit is contained in:
Simon
2026-08-31 14:01:06 +08:00
parent 6acf938caf
commit 73d191b43a
28 changed files with 1418 additions and 373 deletions
+23 -13
View File
@@ -129,9 +129,13 @@ class OptunaEngine:
n_total = len(price_df)
windows = []
test_equities = []
param_history = []
# 跨窗口结转资本:consolidated 是连续可投资的净值曲线,
# 每个测试窗口的收益按期初已积累资本放大,而非各自从 100k 独立重算。
running_capital = float(self.bt_engine.initial_capital)
equity_parts: list[pd.Series] = []
start = 0
while start + train_window + test_window <= n_total:
train_slice = slice(start, start + train_window)
@@ -162,8 +166,14 @@ class OptunaEngine:
test_report = self.bt_engine.run(test_strategy, test_price, test_factor)
if len(test_report.equity_curve) > 0:
test_equities.append(test_report.equity_curve)
# 该窗口的相对收益 → 用累计资本放大 → 连续资本曲线
eq = test_report.equity_curve
if eq is not None and len(eq) > 0:
window_ret = eq.pct_change().fillna(0.0)
# 用上一窗口末累计资本作基准放大本窗口收益
window_capital = running_capital * (1 + window_ret).cumprod()
equity_parts.append(window_capital)
running_capital = float(window_capital.iloc[-1])
train_idx = train_price.index
test_idx = test_price.index
@@ -182,8 +192,8 @@ class OptunaEngine:
start += test_window
# 合并测试期权益曲线
consolidated = _merge_test_periods(test_equities, self.bt_engine.initial_capital)
# 合并测试期权益曲线(跨窗口结转后的连续净值)
consolidated = _merge_test_periods(equity_parts)
# 参数稳定性
param_df = pd.DataFrame(param_history) if param_history else pd.DataFrame()
@@ -198,20 +208,20 @@ class OptunaEngine:
def _merge_test_periods(
equity_list: list[pd.Series],
initial_capital: float = 100_000,
equity_parts: list[pd.Series],
) -> BacktestReport | None:
"""拼接各窗口测试期权益曲线为一个连续序列。"""
if not equity_list:
"""拼接已跨窗口结转资本的测试期净值片段为连续序列。"""
if not equity_parts:
return None
merged = pd.concat(equity_list)
merged = merged.sort_index()
merged = merged[~merged.index.duplicated()]
merged = pd.concat(equity_parts)
merged = merged[~merged.index.duplicated(keep="last")].sort_index()
# 确保 DatetimeIndex
if not isinstance(merged.index, pd.DatetimeIndex):
merged.index = pd.to_datetime(merged.index, format="%Y%m%d")
parsed = pd.to_datetime(merged.index, format="%Y%m%d", errors="coerce")
if parsed.notna().all():
merged.index = parsed
dd = merged / merged.cummax() - 1
daily_ret = merged.pct_change().dropna()