Files
qlib/backend/app/domain/entities/strategy.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

58 lines
2.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.
"""选股策略领域实体(2026-09 重构:原 StrategyDefinition → SelectionStrategy)。
策略库现在**只存选股条件组合**:股票池 + 因子 + 过滤条件。
资金 / 持仓数 / 持仓时间 / 调仓时机 / 费率 / 回测区间一律移到「回测组合」
(BacktestCombo)与「公共配置」(GlobalConfig),在回测时才确定。
仍映射到 `strategy` 表(id/name/description/spec_type/config_json/version/created_at),
config_json 只存 universe/factors/conditions —— 迁移会把旧行的多余键剥掉。
"""
from __future__ import annotations
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field, model_validator
from app.domain.entities.research import ConditionSpec, FactorSpec, UniverseSpec
class SelectionStrategy(BaseModel):
"""一个选股策略 = 选股条件组合(不含任何回测执行参数)。
`extra="forbid"`(2026-09 收尾):请求里若混入旧版的回测执行参数(selection /
rebalance / costs / portfolio / initial_capital / period …),一律**报错**而不是
静默丢弃 —— 否则调用方会以为「在策略上设了费率/调仓」,实际服务端根本没存
(AGENT.md 禁止静默降级与假装支持)。回测参数的正确位置是 BacktestCombo + GlobalConfig。
兼容性:历史行残留的旧键由仓储 `_to_entity` 在**读出前**剔除,因此不受 forbid 影响。
"""
model_config = ConfigDict(extra="forbid")
id: str = ""
name: str = Field(min_length=1, max_length=64)
description: str = ""
# 取值域收敛为 selection:本实体就是「选股策略」。历史 DB 列里的 "backtest"
# 不会被读出(仓储 _to_entity 丢弃该键并回落到默认值),故收紧不会破坏旧数据。
spec_type: str = Field(default="selection", pattern="^selection$")
universe: UniverseSpec = UniverseSpec()
factors: list[FactorSpec] = Field(min_length=1, description="打分因子(至少 1 个)")
conditions: list[ConditionSpec] = Field(
default_factory=list, description="过滤条件(AND,universe 之后、因子排序之前)"
)
version: str = "1"
created_at: datetime | None = None
@model_validator(mode="after")
def _no_duplicate_factors(self) -> SelectionStrategy:
names = [f.name for f in self.factors]
if len(set(names)) != len(names):
raise ValueError("factors 存在重复因子名")
return self
# 兼容别名:重构前到处引用的旧名字。新代码请用 SelectionStrategy;
# 保留别名是为了让尚未迁移的导入点(agent 工具等)在过渡期不炸,
# 最终会全部替换掉(见各调用点的 TODO)。
StrategyDefinition = SelectionStrategy