113 lines
3.5 KiB
Python
113 lines
3.5 KiB
Python
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
|