feat(strategy): 策略实例做实P0+P1+策略库A+B混合布局(spec§12.6定稿)——实例=策略档案:①绑定:paper/live账户+回测任务加instance_id(paper_accounts/live_accounts ALTER迁移),发起即建档(无档案自动建),绑已有档案时D1发起快照(账户用档案参数复印件)②回写:事件型(回测_on_done/回放线程)落盘update_instance_run;持续型(实走/影子/实盘)读时聚合_instance_runtime(四格覆盖+在跑账户+漂移检测)③D2/D3同步:POST /paper/sync/{id}批量刷运行中模拟账户(实走+影子锁死一致),实盘不在线改参④D5删除保护409⑤P1全景:GET instances/{id}/overview(全部运行账户+净值尾部+合并持仓归因)收编挂起项「按实例归因持仓」⑥前端:策略库重做A+B混合(统计条+在跑巡检模式+左栏代码树中文主显/文件副行+档案区漂移角标/同步/全景;STRATEGY_LABELS抽共享常量),InstanceOverview抽屉(echarts净值对比+归因持仓表),模拟盘/实盘表单加实例档案下拉(选中预填+绑定,路由?instance=直进);mock层enriched/sync/overview(路由序enriched先于{id});+7绑定测试,921绿,build绿,dev浏览器验收过 [vps]
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"""策略实例 JSON 存储(配置数据,几行;无需 DB 迁移)。
|
||||
|
||||
实例 = 代码文件的参数变体(spec §12 三层模型中层)。
|
||||
运行态字段 status/last_return 给默认占位,由后续运行关联回填。
|
||||
实例 = 代码文件的参数变体(spec §12 三层模型中层)= 策略档案(§12.6 做实设计)。
|
||||
status/last_return:事件型运行(回测/回放)完成时落盘回写;持续型运行
|
||||
(实走/影子/实盘)由 API 读时聚合覆盖(见 routes_strategy.enrich_instances)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import json
|
||||
@@ -87,3 +88,70 @@ def delete_instance(inst_id: int) -> bool:
|
||||
return False
|
||||
_save(new_rows)
|
||||
return True
|
||||
|
||||
|
||||
# ===== §12.6 做实:事件型运行回写 + 参数漂移(§12.6 P0)=====
|
||||
|
||||
_KINDS = ("backtest", "replay")
|
||||
|
||||
|
||||
def update_instance_run(inst_id: int, kind: str, status: str,
|
||||
ret: float | None = None) -> bool:
|
||||
"""事件型运行(回测/回放)完成/失败时回写档案。
|
||||
|
||||
kind: backtest/replay(paper_live/live 是持续运行,读时聚合不落盘)。
|
||||
ret: 小数收益(0.1548=+15.48%),None 则保留旧值。
|
||||
返回 False = 实例不存在(如已删,静默丢弃)。
|
||||
"""
|
||||
if kind not in _KINDS:
|
||||
return False
|
||||
rows = _load()
|
||||
for r in rows:
|
||||
if r.get("id") == inst_id:
|
||||
r.setdefault("status", dict(_DEFAULT_STATUS))
|
||||
r["status"][kind] = status
|
||||
if ret is not None:
|
||||
r["last_return"] = ret
|
||||
r.setdefault("run_returns", {})
|
||||
r["run_returns"][kind] = ret
|
||||
r["updated_at"] = time.strftime("%Y-%m-%d")
|
||||
_save(rows)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_instance_params_snapshot(inst_id: int) -> dict | None:
|
||||
"""档案当前参数(发起时快照用,D1)。不存在返回 None。"""
|
||||
for r in _load():
|
||||
if r.get("id") == inst_id:
|
||||
return {
|
||||
"params": dict(r.get("params") or {}),
|
||||
"symbol_or_pool": r.get("symbol_or_pool", ""),
|
||||
"interval": r.get("interval", "d"),
|
||||
"match_session": r.get("match_session", "next_open"),
|
||||
"code_file": r.get("code_file", ""),
|
||||
"type": r.get("type", "cta"),
|
||||
"name": r.get("name", ""),
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def account_params_drifted(snapshot_params: dict, account: dict) -> bool:
|
||||
"""参数漂移检测(D2):账户 strategies 参数 vs 档案当前参数。
|
||||
|
||||
account 为 paper_accounts 行(strategies 是 JSON 字符串或已解析 list)。
|
||||
只比对参数键值(标的/周期改了也算漂移——都影响行为)。
|
||||
"""
|
||||
import json as _json
|
||||
raw = account.get("strategies")
|
||||
if isinstance(raw, str):
|
||||
try:
|
||||
strats = _json.loads(raw)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
else:
|
||||
strats = raw or []
|
||||
if not strats:
|
||||
return False
|
||||
live_params = dict(strats[0].get("params") or {})
|
||||
return live_params != dict(snapshot_params.get("params") or {})
|
||||
|
||||
Reference in New Issue
Block a user