Files
qlib/backend/app/api/composites.py
T
Simon 4fa2bb748e feat(composite): M7.2b 因子组合落库 + /api/composites CRUD
- factor_composite 表(migration c3e9a0d1f4b5,MySQL 已应用;name 唯一)
- CompositeDefinition/Component 实体 + CompositeRepository Protocol + SQLAlchemy 实现
- /api/composites:POST(注册表自动填充组件 direction;未注册因子 400)、GET 列表/详情、DELETE
- tests/test_composites_api.py(repo CRUD/同名拒绝/删除、API 方向填充/404/400);全量 pytest 通过
2026-09-09 00:30:37 +08:00

70 lines
2.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.
"""因子组合 API(M7.2b):/api/composites CRUD。
组合 = 可复用因子权重集;计算时方向取因子注册表,落库冗余快照。
"""
from __future__ import annotations
from fastapi import APIRouter, HTTPException
from app.api.deps import CompositeRepoDep, DbSession
from app.application.services.job_executor import new_id
from app.domain.entities.composite import CompositeComponent, CompositeDefinition
from app.quant.factors import FactorError, get_factor
router = APIRouter(prefix="/composites", tags=["composites"])
def _fill_direction(definition: CompositeDefinition) -> CompositeDefinition:
"""以因子注册表元数据补齐/校正组件 direction(登记但不可计算的因子报错)。"""
out = []
for c in definition.components:
try:
defn, _fn = get_factor(c.name)
except FactorError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
out.append(CompositeComponent(name=c.name, weight=c.weight, direction=defn.direction))
return definition.model_copy(update={"components": out})
@router.post("", summary="保存因子组合", response_model=CompositeDefinition)
def create_composite(
definition: CompositeDefinition,
composite_repo: CompositeRepoDep,
session: DbSession,
) -> CompositeDefinition:
prepared = _fill_direction(definition.model_copy(update={"id": ""}))
try:
saved = composite_repo.save(
prepared.model_copy(update={"id": new_id("CF")})
)
session.commit()
return saved
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@router.get("", summary="因子组合列表", response_model=list[CompositeDefinition])
def list_composites(composite_repo: CompositeRepoDep) -> list[CompositeDefinition]:
return composite_repo.list()
@router.get("/{composite_id}", summary="读取因子组合", response_model=CompositeDefinition)
def get_composite(composite_id: str, composite_repo: CompositeRepoDep) -> CompositeDefinition:
row = composite_repo.get(composite_id)
if row is None:
raise HTTPException(status_code=404, detail=f"组合 {composite_id} 不存在")
return row
@router.delete("/{composite_id}", summary="删除因子组合")
def delete_composite(
composite_id: str,
composite_repo: CompositeRepoDep,
session: DbSession,
) -> dict:
if not composite_repo.delete(composite_id):
raise HTTPException(status_code=404, detail=f"组合 {composite_id} 不存在")
session.commit()
return {"deleted": composite_id}