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
+24 -12
View File
@@ -82,34 +82,46 @@ class MLStrategy(BaseStrategy):
class MLBenchmark:
"""ML 模型基准对比测试。"""
"""ML 模型基准对比测试。
入参的 feature_engine 应已用模型训练集 fit 过(scaler/winsor/median 已缓存)。
run() 对 **样本外测试数据**(test_factor_df/test_price_df)用 fit=False 转换后
计算预测 IC,避免"同一时间段既训练又评估"的前视泄漏。
"""
def __init__(
self,
models: list[BaseModel],
feature_engine: FeatureEngine,
price_df: pd.DataFrame,
factor_df: pd.DataFrame,
test_factor_df: pd.DataFrame,
test_price_df: pd.DataFrame,
bt_engine: VectorBTEngine | None = None,
):
self.models = models
self.feature_engine = feature_engine
self.price_df = price_df
self.factor_df = factor_df
self.test_factor_df = test_factor_df
self.test_price_df = test_price_df
self.bt_engine = bt_engine or VectorBTEngine()
def run(self) -> pd.DataFrame:
"""对比各模型的预测质量和回测表现。"""
"""对比各模型在样本外测试集上的预测质量和回测表现。"""
rows = []
# 标签由规则(前视收益)决定,预测时可直接用同一 build_labels 构造,
# 避免依赖 build(fit=False) 不产标签的语义。
y_test = self.feature_engine.build_labels(self.test_price_df)
X_test, _ = self.feature_engine.build(self.test_factor_df, self.test_price_df, fit=False)
if X_test is None or X_test.empty or y_test is None or y_test.dropna().empty:
raise RuntimeError(
"MLBenchmark: 样本外测试集为空或 feature_engine 未在训练集上 fit。")
for model in self.models:
# OOS 回测:MLStrategy 用同一已 fit engine 对测试集生成信号
strategy = MLStrategy(model, self.feature_engine)
report = self.bt_engine.run(strategy, self.price_df, self.factor_df)
report = self.bt_engine.run(strategy, self.test_price_df, self.test_factor_df)
# OOS 预测 vs 真实值
X, y_true = self.feature_engine.build(self.factor_df, self.price_df, fit=True)
y_pred = model.predict(X)
ic = y_pred.corr(y_true) if len(y_pred) > 0 else 0
preds = model.predict(X_test)
common = X_test.index.intersection(y_test.dropna().index)
ic = preds.reindex(common).astype(float).corr(y_test.reindex(common).astype(float)) if len(common) > 1 else 0
rows.append({
"model": model.name,