Files
qlib/backend/app/infrastructure/persistence/sqlalchemy/models/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

34 lines
1.7 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;2026-10 支持参数化实例)。
factor_definition:因子名(主键,参数化实例的参数就写在名字里)→ 元数据;requires 以 JSON 存。
`enabled` 是唯一由人配置的字段(是否出现在因子下拉里);内置实例的开关注由代码注册表
收敛(见 application/services/factor_catalog.py),停用只影响「能不能被选中」,
不影响已引用它的策略/归档解析 —— 历史不能被开关改义。
"""
from __future__ import annotations
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.infrastructure.persistence.sqlalchemy.base import Base
class FactorDefinitionModel(Base):
__tablename__ = "factor_definition"
# 128:参数化实例的名字把参数写全(如 momentum(window=90,direction=lower_is_better))
name: Mapped[str] = mapped_column(String(128), primary_key=True)
description: Mapped[str] = mapped_column(String(500), default="")
formula: Mapped[str] = mapped_column(String(500), default="")
brief: Mapped[str] = mapped_column(String(500), default="")
frequency: Mapped[str] = mapped_column(String(16), default="daily")
lookback: Mapped[int] = mapped_column(Integer, default=20)
direction: Mapped[str] = mapped_column(String(32), default="higher_is_better")
requires_json: Mapped[str] = mapped_column(Text, default="[]")
version: Mapped[str] = mapped_column(String(16), default="1")
enabled: Mapped[bool] = mapped_column(Boolean, default=True, server_default="1")
created_at: Mapped[datetime] = mapped_column(DateTime)