feat(factorminer): 新增 LocalFactorEvaluator 集成到评估管线

- 新增 LocalFactorEvaluator 类封装 FactorEngine,提供 (M,T) 矩阵输出
- evaluate_factors_with_evaluator() 支持新评估方式
- ValidationPipeline 优先使用 evaluator 计算信号
- 新增测试文件验证功能
This commit is contained in:
2026-04-11 01:53:43 +08:00
parent c098631487
commit 45469c8fed
35 changed files with 1176 additions and 2586 deletions

View File

@@ -0,0 +1,117 @@
"""端到端集成测试:验证 110 个 Paper Factors 能被本地引擎计算。
测试目标:
1. 加载 paper factors 库
2. 使用 LocalFactorEvaluator 计算每个因子
3. 验证输出形状为 (M, T) 且不含全 NaN
4. 统计成功率和跳过数
运行命令uv run pytest tests/test_factorminer_e2e.py -v
"""
import pytest
def test_paper_factors_e2e():
"""端到端测试:验证 110 个 Paper Factors 能被本地引擎计算。
注意:此测试需要数据库中有 pro_bar 数据。
如果数据不足,会在计算时失败。
"""
from src.factorminer.core.library_io import import_from_paper
from src.factorminer.evaluation.local_engine import LocalFactorEvaluator
# 1. 加载 paper factors 库
library = import_from_paper()
factors = library.list_factors()
total = len(factors)
print(f"\n[e2e] 总计加载 {total} 个因子")
# 2. 过滤 unsupported 因子
supported_factors = [f for f in factors if not f.metadata.get("unsupported", False)]
unsupported_count = total - len(supported_factors)
print(f"[e2e] 可计算因子 {len(supported_factors)}")
print(f"[e2e] 跳过(未实现算子){unsupported_count}")
if not supported_factors:
print("[e2e] 没有可计算的因子,测试跳过")
pytest.skip("No supported factors to test")
# 3. 实例化 LocalFactorEvaluator
evaluator = LocalFactorEvaluator(
start_date="20200101",
end_date="20201231",
)
# 4. 批量计算每个因子
success_count = 0
fail_count = 0
skip_count = 0
specs = [(f.name, f.formula) for f in supported_factors]
print(f"[e2e] 开始批量计算 {len(specs)} 个因子...")
try:
results = evaluator.evaluate(specs)
except Exception as e:
print(f"[e2e] 批量计算失败: {e}")
pytest.fail(f"Batch evaluation failed: {e}")
# 5. 验证结果
for name, result in results.items():
if result is None:
fail_count += 1
continue
# 检查形状
if result.ndim != 2:
print(f"[e2e] 因子 {name} 形状错误: {result.ndim}D期望 2D")
fail_count += 1
continue
# 检查是否全 NaN
if np.isnan(result).all():
print(f"[e2e] 因子 {name} 输出全为 NaN")
fail_count += 1
continue
success_count += 1
print(
f"[e2e] 成功 {success_count}/{total}"
f"跳过 {skip_count} 个,失败 {fail_count}"
)
# 断言:至少 50% 的因子能成功计算(宽松标准)
success_rate = success_count / total
assert success_rate >= 0.5, f"成功率 {success_rate:.1%} 低于 50% 阈值"
if __name__ == "__main__":
import numpy as np
from src.factorminer.core.library_io import import_from_paper
from src.factorminer.evaluation.local_engine import LocalFactorEvaluator
# 手动运行测试
library = import_from_paper()
factors = library.list_factors()
total = len(factors)
print(f"\n[e2e] 总计加载 {total} 个因子")
supported_factors = [f for f in factors if not f.metadata.get("unsupported", False)]
unsupported_count = total - len(supported_factors)
print(f"[e2e] 可计算因子 {len(supported_factors)}")
print(f"[e2e] 跳过(未实现算子){unsupported_count}")
if supported_factors:
evaluator = LocalFactorEvaluator(
start_date="20200101",
end_date="20201231",
)
specs = [(f.name, f.formula) for f in supported_factors]
print(f"[e2e] 开始批量计算 {len(specs)} 个因子...")
results = evaluator.evaluate(specs)
print(f"[e2e] 批量计算完成,共 {len(results)} 个结果")