"""Tests for Factorminer pipeline integration with LocalFactorEvaluator.""" from __future__ import annotations from typing import Dict, List, Optional, Tuple import numpy as np import pytest from src.factorminer.core.factor_library import FactorLibrary from src.factorminer.core.library_io import import_from_paper from src.factorminer.evaluation.local_engine import LocalFactorEvaluator from src.factorminer.evaluation.pipeline import ( PipelineConfig, ValidationPipeline, run_evaluation_pipeline, ) from src.factorminer.evaluation.runtime import ( evaluate_factors_with_evaluator, ) class TestLocalFactorEvaluatorIntegration: """测试 LocalFactorEvaluator 与评估管线的集成。""" @pytest.fixture def evaluator(self) -> LocalFactorEvaluator: """创建评估器 fixture。""" return LocalFactorEvaluator( start_date="20200101", end_date="20200131", stock_codes=None, ) @pytest.fixture def returns_matrix(self) -> np.ndarray: """创建模拟收益率矩阵 fixture。""" M, T = 100, 20 rng = np.random.default_rng(42) return rng.standard_normal((M, T)) @pytest.fixture def splits(self) -> Dict[str, object]: """创建模拟分割 fixture。""" class MockSplit: def __init__(self, indices: np.ndarray, returns: np.ndarray): self.indices = indices self.returns = returns self.target_returns = {} T = 20 indices = np.arange(T) rng = np.random.default_rng(42) returns = rng.standard_normal((100, T)) return { "train": MockSplit(indices[:15], returns[:, :15]), "val": MockSplit(indices[15:], returns[:, 15:]), } def test_evaluate_factors_with_evaluator_deprecated_path( self, evaluator: LocalFactorEvaluator, returns_matrix: np.ndarray, splits: Dict[str, object], ) -> None: """测试 evaluate_factors_with_evaluator 在有 evaluator 时的行为。""" # 模拟一个因子对象 class MockFactor: def __init__(self, id: str, name: str, formula: str, category: str): self.id = id self.name = name self.formula = formula self.category = category factors = [ MockFactor("f1", "close", "close", "price"), MockFactor("f2", "# TODO: unsupported", "unsupported", "test"), ] try: artifacts = evaluate_factors_with_evaluator( factors=factors, evaluator=evaluator, returns=returns_matrix, splits=splits, ) # 验证返回结果结构 assert len(artifacts) == 2 assert artifacts[0].name == "close" assert artifacts[1].name == "# TODO: unsupported" # unsupported 因子应该被标记 assert artifacts[1].error == "Unsupported operator in formula" except Exception as e: # FactorEngine 可能因为数据不存在而失败 pytest.skip(f"FactorEngine 数据不存在: {e}") def test_evaluate_factors_fallback_legacy( self, returns_matrix: np.ndarray, splits: Dict[str, object], ) -> None: """测试 evaluator=None 时回退到 legacy 方式。""" class MockFactor: def __init__(self, id: str, name: str, formula: str, category: str): self.id = id self.name = name self.formula = formula self.category = category factors = [ MockFactor("f1", "test", "close", "price"), ] # evaluator=None 应该回退到 legacy artifacts = evaluate_factors_with_evaluator( factors=factors, evaluator=None, returns=returns_matrix, splits=splits, ) # Legacy 方式会尝试 compute_tree_signals 但 data_dict 为空 assert len(artifacts) == 1 if __name__ == "__main__": pytest.main([__file__, "-v"])