rebuild(stage-9): deliver deterministic intelligent screening

This commit is contained in:
leefer
2026-07-30 05:15:17 +08:00
parent 6cb52e864a
commit 158257ebb8
46 changed files with 7322 additions and 34 deletions
+112
View File
@@ -0,0 +1,112 @@
from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Path, Query, Request
from backend.data.gateway import MarketDataUnavailable
from backend.data.providers.base import ProviderError
from backend.data.quality import DataQualityError
from backend.features.accounts.auth import (
AdminWritePrincipal,
AuthenticatedPrincipal,
SmartAccessPrincipal,
SmartWritePrincipal,
)
from backend.features.screener.schemas import (
CustomStrategyInput,
IdentifierResponse,
MessageResponse,
ScreenerCatalogResponse,
ScreenerSyncResponse,
ScreenerWorkspaceResponse,
TrackInput,
)
from backend.features.screener.service import ScreenerError
from backend.http.errors import AppError
router = APIRouter(prefix="/screener", tags=["screener"])
@router.get("/catalog", response_model=ScreenerCatalogResponse)
def catalog(request: Request, _principal: AuthenticatedPrincipal) -> dict:
return request.app.state.container.screener.catalog()
@router.get("", response_model=ScreenerWorkspaceResponse)
def workspace(
request: Request,
principal: SmartAccessPrincipal,
requested_date: Annotated[str, Query(alias="date")],
) -> dict:
return _call(request, "workspace", requested_date, principal.user.id)
@router.post("/sync", response_model=ScreenerSyncResponse)
def sync(
request: Request,
_principal: AdminWritePrincipal,
requested_date: Annotated[str, Query(alias="date")],
) -> dict:
return _call(request, "sync_and_run", requested_date)
@router.put("/custom", response_model=dict)
def save_custom(
payload: CustomStrategyInput,
request: Request,
principal: SmartWritePrincipal,
) -> dict:
return _call(request, "save_custom", principal.user.id, payload.name, payload.formula)
@router.delete("/custom/{strategy_id}", response_model=MessageResponse)
def delete_custom(
request: Request,
principal: SmartWritePrincipal,
strategy_id: Annotated[int, Path(gt=0)],
) -> MessageResponse:
_call(request, "delete_custom", principal.user.id, strategy_id)
return MessageResponse(message="自定义策略已删除。")
@router.post("/custom/{strategy_id}/run", response_model=dict)
def run_custom(
request: Request,
principal: SmartWritePrincipal,
strategy_id: Annotated[int, Path(gt=0)],
requested_date: Annotated[str, Query(alias="date")],
) -> dict:
return _call(request, "run_custom", principal.user.id, strategy_id, requested_date)
@router.get("/tracks", response_model=list[dict])
def tracks(request: Request, principal: SmartAccessPrincipal) -> list[dict]:
return _call(request, "tracks", principal.user.id)
@router.post("/tracks", response_model=IdentifierResponse)
def add_track(
payload: TrackInput,
request: Request,
principal: SmartWritePrincipal,
) -> IdentifierResponse:
identifier = _call(request, "add_track", principal.user.id, payload.run_id, payload.identifier)
return IdentifierResponse(id=identifier)
@router.delete("/tracks/{track_id}", response_model=MessageResponse)
def remove_track(
request: Request,
principal: SmartWritePrincipal,
track_id: Annotated[int, Path(gt=0)],
) -> MessageResponse:
_call(request, "remove_track", principal.user.id, track_id)
return MessageResponse(message="已停止跟踪。")
def _call(request: Request, method: str, *args):
try:
return getattr(request.app.state.container.screener, method)(*args)
except (ScreenerError, MarketDataUnavailable, ProviderError, DataQualityError) as exc:
raise AppError("screener_unavailable", str(exc), 409) from exc