Files
qlib/backend/app/domain/entities/factor.py
T
Simon 2e90f3eeac feat(backend): 字段库(condition_field)+ 因子参数化(模板/受控参数)+ 单位换算底座
字段库(本次新增的表与接口):
- `condition_field` 表 + `/api/condition-fields`:中文名/说明可编辑、可停用;
  `kind`/单位阶梯/`base_unit` 由代码注册表收敛(改类型 422,伪字段 422,
  越界单位 422),停用的字段不再进条件下拉,但既有策略仍按名字解析。
- 说明书里的数值条件按字段注册表补**基准单位**后缀(字段间比较不加,不猜单位)。

因子参数化(键即身份,冻结口径):
- 模板 + 参数注册表(`quant/factors.py`):`ParamSpec`(类型/范围/枚举/默认值/说明)+
  `FactorTemplate`(公式/依赖列/参数);规范键把**全部**参数写进名字,如
  `momentum(window=90,direction=lower_is_better)`,所以改参数 = 新建一个身份,
  旧因子/既有策略/已归档实验都不变义;`momentum(window=90)`(缺参数)明确拒绝 ——
  缺项要靠模板默认值补齐,而默认值是可改的代码细节,一旦改动会追溯性改义。
- 参数只在受控范围内取值(窗口 2~500、方向二选一),越界/未知模板/多给参数一律 422
  并列出允许范围,不静默截断、不悄悄取默认值;内置实例的启用开关由代码决定(422)。
- `/api/factors` 暴露 `template`/`params`/`param_specs`/`label`/`source`/`enabled`/
  `resolvable`;新增 `/api/factors/templates`、`POST /api/factors`、`PATCH /api/factors`;
  `get_factor = resolve_factor` 兼容全部旧调用点,参数化键也是一等条件字段。
- 迁移链:c5d6(存量策略陈旧说明重算)→ d6e7(condition_field)→ a7c1
  (factor_definition.enabled + name varchar(128))。

测试:新增 test_condition_fields.py / test_factor_params.py;全量 pytest 500 passed。
2026-10-01 16:33:32 +08:00

91 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""因子目录领域实体(M7.1:因子元数据 DB 化,v2 §11;2026-10 参数化)。
DB 是因子目录的契约源:**哪些因子存在**(含用户从模板派生的参数化实例)入库;
**能不能算**仍由代码注册表(quant/factors.py)唯一决定 —— 登记但解析不出来的因子
在 score/condition 里引用时抛 FactorError(不假装支持)。
参数化的读法:参数化实例的名字本身就是身份(`momentum(window=90,direction=...…)`),
所以 template / params / param_specs / label / source / resolvable 都是**由名字解析出来的
投影**,不落库。落库的只有 `enabled`(是否出现在下拉里)—— 这是人做的配置,
不是引擎事实。好处:参数不可能出现「表里一套、键里一套」的分裂。
"""
from __future__ import annotations
from datetime import datetime
from typing import Any
from pydantic import BaseModel, Field
class FactorParam(BaseModel):
"""一个可编辑参数的约束(与 quant/factors.ParamSpec 对齐,供界面渲染表单)。"""
name: str
label: str = ""
kind: str = "int" # "int" | "enum"
default: Any = None
minimum: int | None = None
maximum: int | None = None
choices: list[str] = Field(default_factory=list)
note: str = ""
@classmethod
def from_spec(cls, spec) -> FactorParam:
return cls(
name=spec.name,
label=spec.label,
kind=spec.kind,
default=spec.default,
minimum=spec.minimum,
maximum=spec.maximum,
choices=list(spec.choices),
note=spec.note,
)
class FactorDefinition(BaseModel):
name: str = Field(min_length=1, max_length=128)
description: str = ""
formula: str = ""
brief: str = ""
frequency: str = "daily"
lookback: int = 20
direction: str = Field(default="higher_is_better", pattern="^(higher_is_better|lower_is_better)$")
requires: list[str] = Field(default_factory=list)
version: str = "1"
created_at: datetime | None = None
# ---- 参数化(落库的只有 enabled;其余由 name 解析投影而来)----
enabled: bool = True
template: str = "" # 模板名,如 "momentum"
params: dict[str, Any] = Field(default_factory=dict) # 冻结的参数取值
param_specs: list[FactorParam] = Field(default_factory=list) # 可编辑参数与约束
label: str = "" # 中文显示名(含参数)
source: str = "builtin" # builtin(代码注册表实例)| custom(目录里的参数化实例)
resolvable: bool = True # False = 登记了但引擎算不出来(历史手工登记行)
@classmethod
def from_registry_def(cls, d) -> FactorDefinition:
"""由 quant/factors.FactorDef(dataclass)构造目录实体(seed 用)。"""
return cls.from_factor_def(d, enabled=True)
@classmethod
def from_factor_def(cls, d, *, enabled: bool = True) -> FactorDefinition:
"""由因子实例(内置或参数化)构造目录实体(seed / 新建用)。"""
return cls(
name=d.name,
description=d.description,
formula=d.formula,
brief=d.brief,
frequency=d.frequency,
lookback=d.lookback,
direction=d.direction,
requires=list(d.requires),
enabled=enabled,
template=d.template,
params=dict(d.params),
param_specs=[FactorParam.from_spec(s) for s in d.param_specs],
label=d.label,
source=d.source,
resolvable=True,
)