"""策略管理路由:代码文件 CRUD + 实例 CRUD(spec §12 三层模型)。 鉴权说明:MVP 不挂 verify_token,便于本地联调与单测;敏感写操作(写文件) 已由 write_strategy_file 的 py_compile 语法门禁兜底。 """ from fastapi import APIRouter, HTTPException from pydantic import BaseModel from .strategy_registry import ( list_strategy_files, read_strategy_file, write_strategy_file, ) from . import instance_store router = APIRouter() class FileWriteRequest(BaseModel): code: str class InstancePayload(BaseModel): code_file: str = "" name: str = "" type: str = "cta" params: dict = {} symbol_or_pool: str = "" interval: str = "d" match_session: str = "next_open" @router.get("/strategy/files") def get_files(): """代码文件列表(不含 code 体)。""" return list_strategy_files() @router.get("/strategy/file/{name}") def get_file(name: str): """读单个策略文件全量(含 code)。""" try: return read_strategy_file(name) except ValueError: raise HTTPException(status_code=404, detail="策略文件不存在") @router.post("/strategy/file/{name}") def post_file(name: str, req: FileWriteRequest): """保存策略代码(py_compile 校验,失败 400)。""" try: write_strategy_file(name, req.code) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) return {"ok": True} @router.post("/strategy/file/{name}/check") def check_file(name: str, req: FileWriteRequest): """语法检查(不落盘):编辑器「语法检查」按钮真接线。 原前端按钮是原型期假实现(sleep 600ms 恒报通过,2026-08-15 用户实况 删括号仍报成功)。compile() 不执行代码,只做语法编译;SyntaxError 返回行号+消息供编辑器定位。 """ try: compile(req.code, name, "exec") except SyntaxError as e: return {"ok": False, "error": str(e.msg or e), "line": e.lineno} except ValueError as e: # null bytes 等 compile 级错误 return {"ok": False, "error": str(e), "line": None} return {"ok": True} @router.get("/strategy/instances") def get_instances(): return instance_store.list_instances() @router.post("/strategy/instances") def post_instance(req: InstancePayload): new_id = instance_store.create_instance(req.model_dump()) return {"id": new_id} @router.get("/strategy/instances/{inst_id}") def get_instance(inst_id: int): return instance_store.get_instance(inst_id) @router.put("/strategy/instances/{inst_id}") def put_instance(inst_id: int, req: InstancePayload): instance_store.update_instance(inst_id, req.model_dump()) return {"ok": True} @router.delete("/strategy/instances/{inst_id}") def del_instance(inst_id: int): ok = instance_store.delete_instance(inst_id) if not ok: raise HTTPException(status_code=404, detail="实例不存在") return {"ok": True}