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 通过
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""因子组合 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}
|
||||
Reference in New Issue
Block a user