"""因子组合 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}