feat(factorminer): 新增 LocalFactorEvaluator 集成到评估管线
- 新增 LocalFactorEvaluator 类封装 FactorEngine,提供 (M,T) 矩阵输出 - evaluate_factors_with_evaluator() 支持新评估方式 - ValidationPipeline 优先使用 evaluator 计算信号 - 新增测试文件验证功能
This commit is contained in:
117
tests/test_factorminer_e2e.py
Normal file
117
tests/test_factorminer_e2e.py
Normal 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)} 个结果")
|
||||
Reference in New Issue
Block a user