204 lines
7.3 KiB
Python
204 lines
7.3 KiB
Python
|
|
"""Domain-owned AI capabilities; caller owns authorization and transactions."""
|
||
|
|
|
||
|
|
from typing import Literal
|
||
|
|
|
||
|
|
from fastapi import HTTPException
|
||
|
|
from pydantic import Field
|
||
|
|
|
||
|
|
from ..ai.capabilities import Capability, EmptyArgs
|
||
|
|
from ..schemas import Contract
|
||
|
|
from .contracts import ControlInput, PreviewInput, RerunInput, StartInput
|
||
|
|
|
||
|
|
|
||
|
|
class BacktestRunArgs(Contract):
|
||
|
|
run_id: str = Field(min_length=1, max_length=36)
|
||
|
|
|
||
|
|
|
||
|
|
class BacktestListArgs(Contract):
|
||
|
|
limit: int = Field(default=20, ge=1, le=100)
|
||
|
|
offset: int = Field(default=0, ge=0)
|
||
|
|
source: str | None = Field(default=None, max_length=100)
|
||
|
|
reference: str | None = Field(default=None, max_length=200)
|
||
|
|
research_id: str | None = Field(default=None, max_length=200)
|
||
|
|
|
||
|
|
|
||
|
|
class BacktestDraftArgs(Contract):
|
||
|
|
draft_id: str = Field(min_length=1, max_length=36)
|
||
|
|
limit: int = Field(default=25, ge=1, le=100)
|
||
|
|
offset: int = Field(default=0, ge=0)
|
||
|
|
|
||
|
|
|
||
|
|
class BacktestResultsArgs(BacktestRunArgs):
|
||
|
|
limit: int = Field(default=20, ge=1, le=100)
|
||
|
|
offset: int = Field(default=0, ge=0)
|
||
|
|
|
||
|
|
|
||
|
|
class BacktestPreviewArgs(Contract):
|
||
|
|
preview_id: str = Field(min_length=1, max_length=36)
|
||
|
|
limit: int = Field(default=20, ge=1, le=100)
|
||
|
|
offset: int = Field(default=0, ge=0)
|
||
|
|
|
||
|
|
|
||
|
|
class BacktestControlArgs(BacktestRunArgs):
|
||
|
|
action: Literal["pause", "resume", "stop", "recover"]
|
||
|
|
|
||
|
|
|
||
|
|
class BacktestRerunArgs(BacktestRunArgs):
|
||
|
|
item_ids: list[str] = Field(min_length=1, max_length=100)
|
||
|
|
|
||
|
|
|
||
|
|
async def draft(ctx, args):
|
||
|
|
data = await ctx.business.backtests.draft(args.draft_id)
|
||
|
|
candidates = data.pop("candidates")
|
||
|
|
return {
|
||
|
|
**data,
|
||
|
|
"items": candidates[args.offset : args.offset + args.limit],
|
||
|
|
"total": len(candidates),
|
||
|
|
"limit": args.limit,
|
||
|
|
"offset": args.offset,
|
||
|
|
"has_more": args.offset + args.limit < len(candidates),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
async def results(ctx, args):
|
||
|
|
data = await ctx.business.backtests.results(**args.model_dump())
|
||
|
|
# Keep the established card shape; complete raw snapshots remain available by business reference.
|
||
|
|
for item in data["items"]:
|
||
|
|
if item["result"]:
|
||
|
|
snapshot = item["result"].pop("snapshot")
|
||
|
|
item["result"].update({k: snapshot.get(k) for k in ("is", "os", "checks", "dateCreated")})
|
||
|
|
return data
|
||
|
|
|
||
|
|
|
||
|
|
async def preview_start(ctx, args):
|
||
|
|
return {"backtest": await ctx.business.backtests.get_preview(args.preview_id)}
|
||
|
|
|
||
|
|
|
||
|
|
async def start(ctx, args, preview):
|
||
|
|
current = await ctx.business.backtests.get_preview(args.preview_id)
|
||
|
|
if current["digest"] != preview["backtest"]["digest"] or current["version"] != args.version:
|
||
|
|
raise HTTPException(409, "回测预览不匹配,请重新确认")
|
||
|
|
return await ctx.business.backtests.start(args)
|
||
|
|
|
||
|
|
|
||
|
|
async def preview_control(ctx, args):
|
||
|
|
return {"backtest_run": await ctx.business.backtests.run(args.run_id), "action": args.action}
|
||
|
|
|
||
|
|
|
||
|
|
async def control(ctx, args, preview):
|
||
|
|
return await ctx.business.backtests.control(
|
||
|
|
args.run_id, ControlInput(action=args.action, version=preview["backtest_run"]["version"])
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
async def wake_backtests(runner, result):
|
||
|
|
"""Only committed, confirmed runs can wake the existing backtest lane."""
|
||
|
|
runner.backtests.wake.set()
|
||
|
|
|
||
|
|
|
||
|
|
INSTRUCTIONS = "回测先读取能力再准备固定候选预览,每次运行确认一次;后续候选新建预览。停止生成不取消回测。\n回测结果追问用 get_backtest/get_backtest_results。上下文或历史没有运行 ID 时,可用 list_backtests 按 source=chatbox 和会话 reference 找回;不得把启动返回当作结果。"
|
||
|
|
|
||
|
|
|
||
|
|
CAPABILITIES = (
|
||
|
|
Capability(
|
||
|
|
name="get_backtest_draft",
|
||
|
|
schema=BacktestDraftArgs,
|
||
|
|
description="分页读取已有候选草稿、版本和来源,随后按 draft_id/draft_version 准备预览。",
|
||
|
|
label="读取候选草稿",
|
||
|
|
renderer="catalog",
|
||
|
|
effect="query",
|
||
|
|
handler=draft,
|
||
|
|
),
|
||
|
|
Capability(
|
||
|
|
name="get_backtest_capabilities",
|
||
|
|
schema=EmptyArgs,
|
||
|
|
description="读取回测输入 schema、支持类型和本地调度配置,不代表平台剩余额度。",
|
||
|
|
label="读取回测能力",
|
||
|
|
renderer="backtest",
|
||
|
|
effect="query",
|
||
|
|
handler=lambda ctx, args: ctx.business.backtests.capabilities(),
|
||
|
|
),
|
||
|
|
Capability(
|
||
|
|
name="prepare_backtest",
|
||
|
|
schema=PreviewInput,
|
||
|
|
description="准备服务端固定回测预览,可用 inline 候选或草稿引用;只保存预览,不提交平台,不需要执行确认。",
|
||
|
|
label="准备回测预览",
|
||
|
|
renderer="backtest",
|
||
|
|
effect="prepare",
|
||
|
|
handler=lambda ctx, args: ctx.business.backtests.preview(args),
|
||
|
|
refresh=("backtests",),
|
||
|
|
),
|
||
|
|
Capability(
|
||
|
|
name="get_backtest_preview",
|
||
|
|
schema=BacktestPreviewArgs,
|
||
|
|
description="分页读取完整固定预览,确认前核对表达式和最终参数。",
|
||
|
|
label="查看回测预览",
|
||
|
|
renderer="backtest",
|
||
|
|
effect="query",
|
||
|
|
handler=lambda ctx, args: ctx.business.backtests.get_preview(**args.model_dump()),
|
||
|
|
),
|
||
|
|
Capability(
|
||
|
|
name="start_backtest",
|
||
|
|
schema=StartInput,
|
||
|
|
description="对已保存预览请求一次用户确认,确认后后台运行全部固定候选,立即返回运行 ID;禁止循环等待。",
|
||
|
|
label="启动固定回测",
|
||
|
|
renderer="backtest",
|
||
|
|
effect="confirm",
|
||
|
|
preview=preview_start,
|
||
|
|
execute=start,
|
||
|
|
after_commit=wake_backtests,
|
||
|
|
refresh=("backtests",),
|
||
|
|
),
|
||
|
|
Capability(
|
||
|
|
name="list_backtests",
|
||
|
|
schema=BacktestListArgs,
|
||
|
|
description="分页查询回测运行与统计,可按来源筛选。",
|
||
|
|
label="查询回测运行",
|
||
|
|
renderer="backtest",
|
||
|
|
effect="query",
|
||
|
|
handler=lambda ctx, args: ctx.business.backtests.runs(**args.model_dump()),
|
||
|
|
),
|
||
|
|
Capability(
|
||
|
|
name="get_backtest",
|
||
|
|
schema=BacktestRunArgs,
|
||
|
|
description="查询指定运行的真实进度,不循环等待完成。",
|
||
|
|
label="查看回测进度",
|
||
|
|
renderer="backtest",
|
||
|
|
effect="query",
|
||
|
|
handler=lambda ctx, args: ctx.business.backtests.run(args.run_id),
|
||
|
|
),
|
||
|
|
Capability(
|
||
|
|
name="get_backtest_results",
|
||
|
|
schema=BacktestResultsArgs,
|
||
|
|
description="分页读取逐项状态、历史指标和错误;未知结果不能推测为成功。",
|
||
|
|
label="读取回测结果",
|
||
|
|
renderer="backtest",
|
||
|
|
effect="query",
|
||
|
|
handler=results,
|
||
|
|
),
|
||
|
|
Capability(
|
||
|
|
name="control_backtest",
|
||
|
|
schema=BacktestControlArgs,
|
||
|
|
description="预览并确认暂停/继续/停止剩余项/找回原任务;不远端取消,不重新提交。",
|
||
|
|
label="控制回测运行",
|
||
|
|
renderer="backtest",
|
||
|
|
effect="confirm",
|
||
|
|
preview=preview_control,
|
||
|
|
execute=control,
|
||
|
|
after_commit=wake_backtests,
|
||
|
|
refresh=("backtests",),
|
||
|
|
),
|
||
|
|
Capability(
|
||
|
|
name="prepare_backtest_rerun",
|
||
|
|
schema=BacktestRerunArgs,
|
||
|
|
description="从明确指定的已结束回测项准备新预览,保留来源;不会自动启动。",
|
||
|
|
label="准备重跑预览",
|
||
|
|
renderer="backtest",
|
||
|
|
effect="prepare",
|
||
|
|
handler=lambda ctx, args: ctx.business.backtests.rerun(
|
||
|
|
args.run_id, RerunInput(item_ids=args.item_ids)
|
||
|
|
),
|
||
|
|
refresh=("backtests",),
|
||
|
|
),
|
||
|
|
)
|