mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 17:54:15 +08:00
修复策略文件无法删除问题
This commit is contained in:
+83
-18
@@ -6,6 +6,7 @@ from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
from dataclasses import asdict
|
||||
@@ -25,6 +26,7 @@ from app.strategy.monitor import StrategyMonitorService
|
||||
from app.strategy.prompt_builder import build_step1, build_step2
|
||||
|
||||
router = APIRouter(prefix="/api/strategies", tags=["strategies"])
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -56,6 +58,57 @@ def _invalidate_strategy_runtime(request: Request) -> None:
|
||||
monitor_engine.invalidate_strategy_state()
|
||||
|
||||
|
||||
def _cleanup_deleted_strategy(request: Request, strategy_id: str) -> list[str]:
|
||||
"""尽力清理删除后的派生状态, 清理失败不应把已成功的源文件删除变成 500。"""
|
||||
from app.services import preferences
|
||||
from app.strategy import monitor_rules
|
||||
|
||||
data_dir = _data_dir(request)
|
||||
warnings: list[str] = []
|
||||
|
||||
try:
|
||||
strategy_config.delete_override(data_dir, strategy_id)
|
||||
except Exception as e:
|
||||
warnings.append(f"覆盖配置清理失败: {e}")
|
||||
|
||||
try:
|
||||
_invalidate_strategy_runtime(request)
|
||||
except Exception as e:
|
||||
warnings.append(f"运行缓存清理失败: {e}")
|
||||
|
||||
try:
|
||||
monitored_ids = preferences.get_strategy_monitor_ids()
|
||||
if strategy_id in monitored_ids:
|
||||
preferences.set_realtime_monitor_config({
|
||||
"strategy_monitor_ids": [sid for sid in monitored_ids if sid != strategy_id],
|
||||
})
|
||||
except Exception as e:
|
||||
warnings.append(f"监控偏好清理失败: {e}")
|
||||
|
||||
try:
|
||||
rules_changed = False
|
||||
for rule in monitor_rules.load_all(data_dir):
|
||||
if (
|
||||
rule.get("type") == "strategy"
|
||||
and rule.get("strategy_id") == strategy_id
|
||||
and rule.get("enabled", True)
|
||||
):
|
||||
rule = dict(rule)
|
||||
rule["enabled"] = False
|
||||
monitor_rules.save_one(data_dir, rule)
|
||||
rules_changed = True
|
||||
|
||||
monitor_engine = getattr(request.app.state, "monitor_engine", None)
|
||||
if rules_changed and monitor_engine is not None:
|
||||
monitor_engine.set_rules(monitor_rules.load_all(data_dir))
|
||||
except Exception as e:
|
||||
warnings.append(f"关联监控清理失败: {e}")
|
||||
|
||||
for warning in warnings:
|
||||
logger.warning("delete strategy %s: %s", strategy_id, warning)
|
||||
return warnings
|
||||
|
||||
|
||||
def _safe(result_dict: dict) -> dict:
|
||||
rows = result_dict.get("rows", [])
|
||||
for r in rows:
|
||||
@@ -763,34 +816,46 @@ async def ai_save(req: AISaveRequest, request: Request):
|
||||
|
||||
@router.delete("/{strategy_id}")
|
||||
def delete_strategy(strategy_id: str, request: Request):
|
||||
"""删除自定义策略 — 清除 .py 文件 + overrides + 热重载。内置策略不可删除。"""
|
||||
"""删除自定义策略 — 清除源文件、运行时注册和关联状态。内置策略不可删除。"""
|
||||
|
||||
engine = _get_engine(request)
|
||||
try:
|
||||
s = engine.get(strategy_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=404, detail=f"策略 {strategy_id} 不存在")
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=f"策略 {strategy_id} 不存在") from e
|
||||
|
||||
if s.source == "builtin":
|
||||
raise HTTPException(status_code=403, detail="内置策略不可删除")
|
||||
|
||||
data_dir = _data_dir(request)
|
||||
path = s.file_path
|
||||
previous_code = path.read_text(encoding="utf-8") if path and path.exists() else None
|
||||
if path and path.exists():
|
||||
path.unlink()
|
||||
try:
|
||||
engine.reload()
|
||||
except Exception as e:
|
||||
if path is not None and previous_code is not None:
|
||||
path.write_text(previous_code, encoding="utf-8")
|
||||
engine.reload()
|
||||
raise HTTPException(status_code=400, detail=f"策略删除失败: {e}") from e
|
||||
data_dir = _data_dir(request)
|
||||
if path is None or s.source not in {"custom", "ai"}:
|
||||
raise HTTPException(status_code=400, detail="策略源文件路径无效, 无法删除")
|
||||
|
||||
override_path = data_dir / "user_data" / "strategy_overrides" / f"{strategy_id}.json"
|
||||
override_path.unlink(missing_ok=True)
|
||||
_invalidate_strategy_runtime(request)
|
||||
return {"ok": True}
|
||||
try:
|
||||
allowed_dir = (data_dir / "strategies" / s.source).resolve()
|
||||
resolved_path = path.resolve()
|
||||
except (OSError, RuntimeError) as e:
|
||||
raise HTTPException(status_code=409, detail=f"无法访问策略文件: {e}") from e
|
||||
if not resolved_path.is_relative_to(allowed_dir):
|
||||
raise HTTPException(status_code=400, detail="策略源文件不在用户策略目录, 拒绝删除")
|
||||
|
||||
try:
|
||||
resolved_path.unlink(missing_ok=True)
|
||||
except OSError as e:
|
||||
reason = e.strerror or str(e)
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail=(
|
||||
f"无法删除策略文件 {resolved_path.name}: {reason}。"
|
||||
"请确认数据目录可写; Docker 部署请检查数据卷不是只读挂载。"
|
||||
),
|
||||
) from e
|
||||
|
||||
# 删除只影响当前策略, 全量 reload 会让其他损坏或重复 ID 的文件阻塞本次删除。
|
||||
engine.unregister(strategy_id)
|
||||
warnings = _cleanup_deleted_strategy(request, strategy_id)
|
||||
return {"ok": True, "warnings": warnings}
|
||||
|
||||
|
||||
# ── 监控 ─────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user