Files
worldquant-alpha-system/backend/app/backtests/routes.py
T

198 lines
7.0 KiB
Python
Raw Normal View History

"""Authenticated adapters; every mutation is committed before the execution lane wakes."""
from typing import Literal
from fastapi import APIRouter, Depends, Query, Request
from ..business import Business
from ..research.contracts import ResearchPreviewInput
from ..research.service import ResearchBuilder
from ..security import require_auth
from .contracts import (
ControlInput,
DraftInput,
DraftUpdate,
EventPage,
PreviewInput,
PreviewOutput,
ReferenceInput,
RerunInput,
ResultPage,
RunOutput,
RunPage,
SchedulerInput,
SchedulerOutput,
StartInput,
SubsetInput,
)
router = APIRouter(prefix="/api/v1/backtests", tags=["backtests"], dependencies=[Depends(require_auth)])
@router.get("/capabilities")
async def capabilities(request: Request):
async with request.app.state.sessions() as db:
return await Business(db).backtests.capabilities()
@router.get("/config", response_model=SchedulerOutput)
async def config(request: Request):
async with request.app.state.sessions() as db:
return await Business(db).backtests.config()
@router.put("/config", response_model=SchedulerOutput)
async def configure(body: SchedulerInput, request: Request):
async with request.app.state.sessions.begin() as db:
result = await Business(db).backtests.configure(body)
request.app.state.runner.backtests.wake.set()
return result
@router.get("/drafts")
async def drafts(
request: Request,
limit: int = Query(25, ge=1, le=100),
offset: int = Query(0, ge=0),
q: str = Query("", max_length=200),
sort: Literal["name", "updated_at"] = "updated_at",
direction: Literal["asc", "desc"] = "desc",
):
async with request.app.state.sessions() as db:
return await Business(db).backtests.drafts(limit, offset, q, sort, direction)
@router.post("/drafts", status_code=201)
async def save_draft(body: DraftInput, request: Request):
async with request.app.state.sessions.begin() as db:
return await Business(db).backtests.save_draft(body)
@router.get("/drafts/{draft_id}")
async def draft(draft_id: str, request: Request):
async with request.app.state.sessions() as db:
return await Business(db).backtests.draft(draft_id)
@router.put("/drafts/{draft_id}")
async def update_draft(draft_id: str, body: DraftUpdate, request: Request):
async with request.app.state.sessions.begin() as db:
return await Business(db).backtests.save_draft(body, draft_id)
@router.post("/previews", status_code=201, response_model=PreviewOutput)
async def preview(body: PreviewInput, request: Request):
async with request.app.state.sessions.begin() as db:
return await Business(db).backtests.preview(body)
@router.post("/research-previews", status_code=201, response_model=PreviewOutput)
async def research_preview(body: ResearchPreviewInput, request: Request):
"""Prepare typed field bindings for any research producer; never start a simulation."""
async with request.app.state.sessions.begin() as db:
return await ResearchBuilder(db, Business(db).backtests).prepare(body)
@router.get("/previews/{preview_id}", response_model=PreviewOutput)
async def get_preview(
preview_id: str, request: Request, limit: int = Query(25, ge=1, le=100), offset: int = Query(0, ge=0)
):
async with request.app.state.sessions() as db:
return await Business(db).backtests.get_preview(preview_id, limit, offset)
@router.post("/runs", status_code=202, response_model=RunOutput)
async def start(body: StartInput, request: Request):
async with request.app.state.sessions.begin() as db:
result = await Business(db).backtests.start(body)
request.app.state.runner.backtests.wake.set()
return result
@router.get("/runs", response_model=RunPage)
async def runs(
request: Request,
limit: int = Query(25, ge=1, le=100),
offset: int = Query(0, ge=0),
source: str | None = Query(None, max_length=100),
reference: str | None = Query(None, max_length=200),
research_id: str | None = Query(None, max_length=200),
q: str = Query("", max_length=200),
sort: Literal["name", "created_at"] = "created_at",
direction: Literal["asc", "desc"] = "desc",
):
async with request.app.state.sessions() as db:
return await Business(db).backtests.runs(
limit, offset, source, reference, research_id, q, sort, direction
)
@router.get("/sources", response_model=list[str])
async def sources(request: Request):
async with request.app.state.sessions() as db:
return await Business(db).backtests.sources()
@router.get("/runs/{run_id}", response_model=RunOutput)
async def run(run_id: str, request: Request):
async with request.app.state.sessions() as db:
return await Business(db).backtests.run(run_id)
@router.get("/runs/{run_id}/results", response_model=ResultPage)
async def results(
run_id: str, request: Request, limit: int = Query(25, ge=1, le=100), offset: int = Query(0, ge=0)
):
async with request.app.state.sessions() as db:
return await Business(db).backtests.results(run_id, limit, offset)
@router.get("/runs/{run_id}/events", response_model=EventPage)
async def events(
run_id: str, request: Request, after: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=100)
):
async with request.app.state.sessions() as db:
return await Business(db).backtests.events(run_id, after, limit)
@router.get("/runs/{run_id}/attempts")
async def attempts(run_id: str, request: Request):
async with request.app.state.sessions() as db:
return await Business(db).backtests.attempts(run_id)
@router.post("/runs/{run_id}/control", response_model=RunOutput)
async def control(run_id: str, body: ControlInput, request: Request):
async with request.app.state.sessions.begin() as db:
result = await Business(db).backtests.control(run_id, body)
request.app.state.runner.backtests.wake.set()
return result
@router.post("/runs/{run_id}/rerun-preview", status_code=201, response_model=PreviewOutput)
async def rerun(run_id: str, body: RerunInput, request: Request):
async with request.app.state.sessions.begin() as db:
return await Business(db).backtests.rerun(run_id, body)
@router.post("/attempts/{attempt_id}/reference", response_model=RunOutput)
async def attach_reference(attempt_id: str, body: ReferenceInput, request: Request):
from fastapi import HTTPException
from ..worldquant import WqError
try:
body.progress_url = request.app.state.runner.client.simulation_url(body.progress_url)
except WqError as exc:
raise HTTPException(422, str(exc)) from None
async with request.app.state.sessions.begin() as db:
result = await Business(db).backtests.attach_reference(attempt_id, body)
request.app.state.runner.backtests.wake.set()
return result
@router.post("/previews/{preview_id}/subset", status_code=201, response_model=PreviewOutput)
async def subset(preview_id: str, body: SubsetInput, request: Request):
async with request.app.state.sessions.begin() as db:
return await Business(db).backtests.subset(preview_id, body)