feat(factors): 集成 metadata 模块,支持按名称注册因子

- 新增 add_factor_by_name() 方法,从 metadata 查询 DSL 表达式并注册
- FactorEngine 支持可选的 metadata_path 参数初始化
- 将 regression.ipynb 和 learn_to_rank.ipynb 转换为 Python 脚本
- 新增 test_factor_engine_metadata.py 测试文件
This commit is contained in:
2026-03-11 22:54:52 +08:00
parent 038f5f1722
commit 2bb7718dd1
7 changed files with 2085 additions and 3101 deletions

View File

@@ -16,6 +16,7 @@ import polars as pl
if TYPE_CHECKING:
from src.factors.registry import FunctionRegistry
from src.factors.metadata import FactorManager
from src.factors.dsl import (
Node,
@@ -57,6 +58,7 @@ class FactorEngine:
data_source: Optional[Dict[str, pl.DataFrame]] = None,
max_workers: int = 4,
registry: Optional["FunctionRegistry"] = None,
metadata_path: Optional[str] = None,
) -> None:
"""初始化因子引擎。
@@ -64,6 +66,7 @@ class FactorEngine:
data_source: 内存数据源,为 None 时使用数据库连接
max_workers: 并行计算的最大工作线程数
registry: 函数注册表None 时创建独立实例
metadata_path: 因子元数据文件路径,为 None 时不启用 metadata 功能
"""
from src.factors.registry import FunctionRegistry
from src.factors.parser import FormulaParser
@@ -78,6 +81,13 @@ class FactorEngine:
self._registry = registry if registry is not None else FunctionRegistry()
self._parser = FormulaParser(self._registry)
# 初始化 metadata 管理器(可选)
self._metadata: Optional["FactorManager"] = None
if metadata_path is not None:
from src.factors.metadata import FactorManager
self._metadata = FactorManager(metadata_path)
def register(
self,
name: str,
@@ -175,6 +185,76 @@ class FactorEngine:
# 委托给现有的 register 方法
return self.register(name, node, data_specs)
def add_factor_by_name(
self,
name: str,
factor_name_in_metadata: Optional[str] = None,
data_specs: Optional[List[DataSpec]] = None,
) -> "FactorEngine":
"""根据 metadata 中的因子名称注册因子。
从 metadata 管理器中根据因子名称查询 DSL 表达式,
然后解析并注册到引擎中。
Args:
name: 要注册的因子名称(引擎中使用的名称)
factor_name_in_metadata: metadata 中的因子名称,
为 None 时默认使用 name 参数
data_specs: 可选的数据规格
Returns:
self支持链式调用
Raises:
RuntimeError: 当引擎未配置 metadata 路径时
ValueError: 当在 metadata 中未找到因子时
FormulaParseError: 当 DSL 表达式解析失败时
Example:
>>> # 初始化时启用 metadata
>>> engine = FactorEngine(metadata_path="data/factors.jsonl")
>>>
>>> # 注册 metadata 中的因子(使用相同名称)
>>> engine.add_factor_by_name("return_5")
>>>
>>> # 使用不同名称注册
>>> engine.add_factor_by_name("my_mom", "momentum_5d")
>>>
>>> # 链式调用
>>> (engine
... .add_factor_by_name("ma20")
... .add_factor_by_name("rsi14")
... .compute(["ma20", "rsi14"], "20240101", "20240131"))
"""
if self._metadata is None:
raise RuntimeError(
"引擎未配置 metadata 路径。请在初始化时传入 metadata_path 参数,"
+ "例如FactorEngine(metadata_path='data/factors.jsonl')"
)
# 使用传入的名称或默认使用 name
query_name = (
factor_name_in_metadata if factor_name_in_metadata is not None else name
)
# 从 metadata 查询因子
df = self._metadata.get_factors_by_name(query_name)
if len(df) == 0:
raise ValueError(
f"在 metadata 中未找到因子 '{query_name}'"
+ "请确认因子名称正确,或先使用 FactorManager 添加该因子。"
)
# 获取 DSL 表达式
dsl_expr = df["dsl"][0]
# 解析表达式为 Node
node = self._parser.parse(dsl_expr)
# 委托给 register 方法
return self.register(name, node, data_specs)
def compute(
self,
factor_names: Union[str, List[str]],