docs(phase3b): Phase 3b Vue 前端实施计划(S0-S3,含 result_id bug 修复)
This commit is contained in:
@@ -0,0 +1,405 @@
|
||||
# Phase 3b 投研+回测 Web 控制台 实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 建一个 Vue 3 前端控制台(投研+回测),对齐 vnpy client 回测模块,从公网 `vnpy.mysanguo.top` 可用。
|
||||
|
||||
**Architecture:** Vue 3 SPA(Vite 构建)→ FastAPI `sanguo_api`(容器:8000,从旧 `sanguo_web` 切过来)静态挂 `/` + 研究 API 在 `/api/v1/*`;后端调 `sanguo_backtest`/`sanguo_factor` 引擎。4 切片 S0→S1→S2→S3,每片可独立演示+测试。
|
||||
|
||||
**Tech Stack:** Vue3 + Vite + TypeScript + Element Plus + ECharts + Pinia + Vue Router + Axios;后端 FastAPI + pytest(现有);容器 Python 3.10。
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- **端口/反代红线**:容器 8000 不变;不碰 `vnpy.mysanguo.top` 的 frpc/socat/Caddy。只改容器内 uvicorn 目标 + 静态挂载。
|
||||
- **vnpy 零修改**:不改 `vnpy_v4.4.0/`;策略/参数从类属性读。
|
||||
- **配置源**:`config/backtest.yaml`(`backtest.db_path`/`file_dir`、`auth.username/password_hash/jwt_secret/token_expire_minutes`、`api.port`)。
|
||||
- **NAS 访问**:`ssh sanguo-nas`(key 免密);docker 全路径 `/var/packages/Docker/target/usr/bin/docker`;rsync/scp 不稳时用 `ssh sanguo-nas "cat > /path" < local`。
|
||||
- **默认登录**:admin / admin(`backtest.yaml` 的 password_hash;部署后改)。
|
||||
- **A 股 DB**:`/volume1/stock/sanguo_vnpy/data/quant_trading.db`(K线,via `read_db_daily`,bare symbol 如 `600000`)。
|
||||
- **JSON 安全**:所有新接口返回值 Timestamp→str、DataFrame→list[dict]。
|
||||
- **TDD**:后端每个新接口先写 pytest 失败测试;前端关键逻辑(auth store/api client)用 Vitest。
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
**前端(新建 `frontend/`):**
|
||||
```
|
||||
frontend/
|
||||
├── package.json, vite.config.ts, tsconfig.json, index.html
|
||||
├── src/
|
||||
│ ├── main.ts, App.vue
|
||||
│ ├── router/index.ts # 路由 + 登录守卫
|
||||
│ ├── stores/auth.ts # Pinia: token/user
|
||||
│ ├── api/client.ts # axios 实例 + JWT 拦截器 + 401 处理
|
||||
│ ├── api/backtest.ts, api/factor.ts, api/strategy.ts
|
||||
│ ├── composables/useTask.ts # 任务状态轮询 + WS
|
||||
│ ├── views/Login.vue, Layout.vue # Layout = 4 入口侧栏 shell
|
||||
│ ├── views/backtest/{New,Progress,Result,Optimize,History}.vue
|
||||
│ ├── views/factor/{New,Result}.vue
|
||||
│ └── components/charts/{EquityChart,DailyPnlChart,KlineChart}.vue
|
||||
│ components/TradesTable.vue
|
||||
└── tests/{auth.test.ts,client.test.ts} # vitest
|
||||
```
|
||||
|
||||
**后端(修改/新建):**
|
||||
```
|
||||
sanguo_api/main.py # 新:读 backtest.yaml → create_app → 暴露 app(uvicorn 目标)
|
||||
sanguo_api/app.py # 改:create_app 加 static_dir 参数,挂 SPA + history fallback
|
||||
sanguo_api/routes.py # 改:加 strategy/factor/kline/equity/daily-pnl/trades/ic-summary/report/opt-results/task-list
|
||||
sanguo_api/schemas.py # 改(S3):CtaBacktestRequest 加 rate/slippage/capital
|
||||
sanguo_api/strategy_registry.py # 新:枚举 vnpy_ctastrategy 策略 + 参数
|
||||
sanguo_api/kline.py # 新:read_db_daily → K线 list[dict]
|
||||
sanguo_backtest/result_store.py # 改:BacktestResult 加 id;save_result 设 result.id
|
||||
sanguo_backtest/cta_engine.py # 改:构建 equity_curve/trades DataFrame;save 传 file_dir
|
||||
sanguo_orchestrator/runner.py # 改:_on_done 用 result.id(修 bug)
|
||||
docker/entrypoint.sh # 改:uvicorn sanguo_web.api:app → sanguo_api.main:app
|
||||
scripts/smoke_phase3b.py # 新:端到端冒烟(登录→回测→进度→结果接口齐)
|
||||
tests/api/test_*.py # 新接口单测
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 切片 S0:脚手架 + 切 app + 登录
|
||||
|
||||
## Task S0.1:sanguo_api/main.py —— 容器 app 入口
|
||||
|
||||
**Files:**
|
||||
- Create: `sanguo_api/main.py`
|
||||
- Test: `tests/api/test_main.py`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `sanguo_api.main:app`(模块级 FastAPI,供 uvicorn),`sanguo_api.main.build_app(config_path: str, static_dir: str | None) -> FastAPI`
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
```python
|
||||
# tests/api/test_main.py
|
||||
from sanguo_api.main import build_app
|
||||
|
||||
def _cfg(tmp_path):
|
||||
cfg = tmp_path / "bt.yaml"
|
||||
cfg.write_text(
|
||||
"backtest:\n max_workers: 1\n db_path: %s\n file_dir: %s\n"
|
||||
"api:\n host: 0.0.0.0\n port: 8000\n"
|
||||
"auth:\n username: admin\n password_hash: x\n jwt_secret: s\n token_expire_minutes: 60\n"
|
||||
"pool:\n max_workers: 1\n" % (tmp_path / "r.db", tmp_path / "f")
|
||||
)
|
||||
return str(cfg)
|
||||
|
||||
def test_build_app_has_api_routes(tmp_path):
|
||||
app = build_app(_cfg(tmp_path))
|
||||
paths = [getattr(r, "path", "") for r in app.routes]
|
||||
assert "/api/v1/auth/login" in paths
|
||||
|
||||
def test_build_app_mounts_spa(tmp_path):
|
||||
spa = tmp_path / "spa"; spa.mkdir(); (spa / "index.html").write_text("<h1>SPA</h1>")
|
||||
app = build_app(_cfg(tmp_path), static_dir=str(spa))
|
||||
assert any(getattr(r, "path", "") == "/" for r in app.routes)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行验证失败** — `pytest tests/api/test_main.py -v` → FAIL(module not found)
|
||||
|
||||
- [ ] **Step 3: 实现 main.py**
|
||||
|
||||
```python
|
||||
# sanguo_api/main.py
|
||||
"""容器 uvicorn 入口:读 config/backtest.yaml 构建 app,暴露模块级 `app`。"""
|
||||
from __future__ import annotations
|
||||
import os
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from starlette.responses import FileResponse
|
||||
|
||||
from .app import create_app as _create_app
|
||||
|
||||
|
||||
def _load_config(config_path: str) -> dict:
|
||||
p = Path(config_path)
|
||||
if not p.exists():
|
||||
return {}
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
|
||||
|
||||
def build_app(config_path: str = "config/backtest.yaml", static_dir: str | None = None) -> FastAPI:
|
||||
cfg = _load_config(config_path)
|
||||
bt = cfg.get("backtest", {})
|
||||
auth = cfg.get("auth", {})
|
||||
pool = cfg.get("pool", {})
|
||||
db_path = bt.get("db_path", "/tmp/backtest_results.db")
|
||||
file_dir = bt.get("file_dir", "/tmp/backtest_files")
|
||||
auth_config = {
|
||||
"username": auth.get("username", "admin"),
|
||||
"password_hash": auth.get("password_hash", ""),
|
||||
"jwt_secret": auth.get("jwt_secret", "change-me"),
|
||||
"expire_minutes": auth.get("token_expire_minutes", 60),
|
||||
} if auth else None
|
||||
max_workers = pool.get("max_workers", bt.get("max_workers", 2))
|
||||
|
||||
app = _create_app(db_path=db_path, file_dir=file_dir, auth_config=auth_config, max_workers=max_workers)
|
||||
|
||||
# SPA 静态挂载(history fallback)。根路由先注册,再 mount 兜底。
|
||||
if static_dir and os.path.isdir(static_dir):
|
||||
index_html = os.path.join(static_dir, "index.html")
|
||||
|
||||
@app.get("/", include_in_schema=False)
|
||||
async def _spa_root():
|
||||
return FileResponse(index_html)
|
||||
|
||||
app.mount("/", StaticFiles(directory=static_dir, html=True), name="spa")
|
||||
|
||||
return app
|
||||
|
||||
|
||||
_REPO = Path(__file__).resolve().parent.parent
|
||||
app = build_app(
|
||||
str(_REPO / "config" / "backtest.yaml"),
|
||||
static_dir=os.environ.get("SPA_STATIC_DIR", str(_REPO / "frontend" / "dist")),
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 运行验证通过** — `pytest tests/api/test_main.py -v` → PASS
|
||||
- [ ] **Step 5: 提交** — `git add sanguo_api/main.py tests/api/test_main.py && git commit -m "feat(api): sanguo_api.main 容器入口(读 backtest.yaml + 挂 SPA)"`
|
||||
|
||||
---
|
||||
|
||||
## Task S0.2:切 docker/entrypoint.sh uvicorn 目标
|
||||
|
||||
**Files:** Modify: `docker/entrypoint.sh:282-283`
|
||||
|
||||
- [ ] **Step 1: 改目标** — `sanguo_web.api:app` → `sanguo_api.main:app`(两行 log_info + exec)
|
||||
- [ ] **Step 2: 本地冒烟** — `python -c "from sanguo_api.main import app; print([getattr(r,'path','') for r in app.routes][:5])"` 含 `/api/v1/auth/login`
|
||||
- [ ] **Step 3: 提交** — `git commit -am "chore(deploy): 容器 uvicorn 切到 sanguo_api.main:app"`
|
||||
|
||||
---
|
||||
|
||||
## Task S0.3:前端脚手架
|
||||
|
||||
**Files:** Create: `frontend/{package.json,vite.config.ts,tsconfig.json,index.html,src/main.ts,src/App.vue,src/env.d.ts}`
|
||||
|
||||
- [ ] **Step 1: 初始化** — `npm create vite@latest frontend -- --template vue-ts && cd frontend && npm install && npm install element-plus echarts pinia vue-router axios && npm install -D vitest @vue/test-utils jsdom @types/node`
|
||||
- [ ] **Step 2: vite.config.ts**(alias @→src;dev proxy /api、/ws → 192.168.2.154:8000;build outDir=dist;vitest jsdom)
|
||||
- [ ] **Step 3: main.ts** 挂 Pinia + Router + ElementPlus;App.vue = `<router-view/>`
|
||||
- [ ] **Step 4: `npm run build`** → `dist/index.html` 生成
|
||||
- [ ] **Step 5: `.gitignore`** 加 `frontend/node_modules/`、`frontend/dist/`
|
||||
- [ ] **Step 6: 提交** — `git add frontend/ .gitignore && git commit -m "feat(frontend): Vue3+Vite+TS 脚手架"`
|
||||
|
||||
---
|
||||
|
||||
## Task S0.4:前端 auth(client + store + 登录页 + 守卫)
|
||||
|
||||
**Files:** Create: `src/api/client.ts`, `src/stores/auth.ts`, `src/views/Login.vue`, `src/router/index.ts`, `tests/auth.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写失败测试**(auth store setToken/logout/isAuthenticated,见 plan 源)
|
||||
- [ ] **Step 2: 验证失败** → `npx vitest run tests/auth.test.ts` FAIL
|
||||
- [ ] **Step 3: stores/auth.ts**(Pinia,token/username 持久化 localStorage;isAuthenticated getter;setToken/logout actions)
|
||||
- [ ] **Step 4: api/client.ts**(axios baseURL=/api/v1;请求拦截附 Bearer;响应 401→logout+跳登录)
|
||||
- [ ] **Step 5: router/index.ts**(路由表 + beforeEach 未登录跳 /login)
|
||||
- [ ] **Step 6: Login.vue**(用户名+密码 → POST /auth/login → setToken → push '/';失败 ElMessage)
|
||||
- [ ] **Step 7: 验证通过** → `npx vitest run` PASS
|
||||
- [ ] **Step 8: 提交** — `git commit -am "feat(frontend): auth(JWT store + axios 拦截器 + 登录页 + 路由守卫)"`
|
||||
|
||||
---
|
||||
|
||||
## Task S0.5:Layout shell(4 入口侧栏)
|
||||
|
||||
**Files:** Create: `src/views/Layout.vue`
|
||||
|
||||
- [ ] **Step 1: Layout.vue** — el-container + el-menu 侧栏 4 项(回测/投研 active;模拟/实盘 disabled"敬请期待");顶栏 用户名+登出
|
||||
- [ ] **Step 2: `npm run build`** 验证
|
||||
- [ ] **Step 3: 提交** — `git commit -am "feat(frontend): Layout shell(4 入口侧栏,模拟/实盘灰显)"`
|
||||
|
||||
---
|
||||
|
||||
## Task S0.6:部署 S0 + 公网验证
|
||||
|
||||
- [ ] **Step 1: 本机 `cd frontend && npm run build`**
|
||||
- [ ] **Step 2: 同步 NAS**(rsync frontend/ + ssh-exec 传 main.py/entrypoint.sh)
|
||||
- [ ] **Step 3: `ssh sanguo-nas "$DOCKER restart sanguo_vnpy_v2"`**
|
||||
- [ ] **Step 4: 公网验证** — `curl https://vnpy.mysanguo.top/` 返回 SPA index;`POST /api/v1/auth/login` admin/admin 返回 token
|
||||
- [ ] **Step 5: 浏览器验收** — 登录 → 空壳控制台(侧栏 4 入口)
|
||||
|
||||
---
|
||||
|
||||
# 切片 S1:回测核心(对齐 vnpy client 回测模块)
|
||||
|
||||
## Task S1.1:修 result_id bug(阻塞所有结果查看)
|
||||
|
||||
**Files:** Modify: `result_store.py`(加 id)、`runner.py:_on_done`(用 result.id)、`cta_engine.py`(save 传 file_dir);Test: `tests/backtest/test_result_store.py`
|
||||
|
||||
**Interfaces — Produces:** `BacktestResult.id: int | None`;`save_result` 后 `result.id`=DB 行 id;`get_result` 正确 load_result(result.id);equity_curve 经 parquet 落盘读回
|
||||
|
||||
- [ ] **Step 1: 写失败测试**(save→设 r.id→load_result(r.id) 读回 statistics+equity_curve)
|
||||
- [ ] **Step 2: 验证失败** → FAIL
|
||||
- [ ] **Step 3: result_store.py** — dataclass 加 `id: Optional[int] = None`;`save_result` commit 后 `result.id = cur.lastrowid`
|
||||
- [ ] **Step 4: runner._on_done** — `task.complete(result_id=result.id)`
|
||||
- [ ] **Step 5: cta_engine** — save_result 传 file_dir(cfg 或 `os.path.dirname(db_path)` 兜底)
|
||||
- [ ] **Step 6: 验证通过** → PASS
|
||||
- [ ] **Step 7: 提交** — `git commit -am "fix(backtest): result_id 用 DB 行 id;equity_curve 落盘读回(修 get_result bug)"`
|
||||
|
||||
---
|
||||
|
||||
## Task S1.2:cta_engine 构建 equity_curve + trades DataFrame
|
||||
|
||||
**Files:** Modify: `cta_engine.py`
|
||||
|
||||
**Interfaces — Produces:** `equity_curve`=DataFrame[date,balance](engine.get_all_daily_results);`trades`=DataFrame[datetime,direction,offset,price,volume,vt_symbol](engine.trades)
|
||||
|
||||
- [ ] **Step 1: calculate_statistics 后构建 DataFrame**(try/except 兜底版本差异)
|
||||
- [ ] **Step 2: result 用 equity_curve=equity_df, trades=trades_df(替换原 daily_results/None)**
|
||||
- [ ] **Step 3: 容器 diag_cta.py 验证** result.equity_curve/trades 非空 + result.id 有值
|
||||
- [ ] **Step 4: 提交** — `git commit -am "feat(backtest): cta_engine 构建 equity_curve/trades 并落盘"`
|
||||
|
||||
---
|
||||
|
||||
## Task S1.3:strategy_registry —— 枚举 vnpy_ctastrategy 策略
|
||||
|
||||
**Files:** Create: `sanguo_api/strategy_registry.py`;Test: `tests/api/test_strategy_registry.py`
|
||||
|
||||
**Interfaces — Produces:** `list_strategies()->list[{name,class_name}]`;`strategy_params(name)->{parameters,defaults}`;`get_strategy_class(name)->type|None`;常量 `STRATEGY_NAMES`
|
||||
|
||||
- [ ] **Step 1: 写失败测试**(list shape;params 含 parameters list)
|
||||
- [ ] **Step 2: 验证失败** → FAIL
|
||||
- [ ] **Step 3: 实现**(pkgutil 枚举 vnpy_ctastrategy.strategies;导入失败兜底 STRATEGY_NAMES=[DoubleMaStrategy,BollChannelStrategy,AtrRsiStrategy])
|
||||
- [ ] **Step 4: 验证通过** → PASS
|
||||
- [ ] **Step 5: 提交** — `git commit -am "feat(api): strategy_registry 枚举策略与参数"`
|
||||
|
||||
---
|
||||
|
||||
## Task S1.4:回测后端接口(strategy + equity/pnl/trades)
|
||||
|
||||
**Files:** Modify: `routes.py`;Test: `tests/api/test_backtest_routes_switch.py`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: orchestrator.get_result(id).equity_curve/trades;strategy_registry
|
||||
- Produces: `GET /strategy/list`、`/strategy/{name}/params`、`/task/{id}/equity-curve`、`/task/{id}/daily-pnl`、`/task/{id}/trades`
|
||||
|
||||
- [ ] **Step 1: 写失败测试**(FakeOrch.get_result 返回带 equity 的 BacktestResult;无 token 401;有 token 200 + records)
|
||||
- [ ] **Step 2: 验证失败** → FAIL
|
||||
- [ ] **Step 3: 加路由** + `_df_to_records(df)` 工具(DataFrame→list[dict],空安全);daily-pnl 由 balance.diff 计算
|
||||
- [ ] **Step 4: 验证通过** → PASS
|
||||
- [ ] **Step 5: 提交** — `git commit -am "feat(api): 回测结果接口(strategy + equity-curve/daily-pnl/trades)"`
|
||||
|
||||
---
|
||||
|
||||
## Task S1.5:K线接口 `/kline`
|
||||
|
||||
**Files:** Create: `sanguo_api/kline.py`;Modify: `routes.py`;Test: `tests/api/test_kline.py`
|
||||
|
||||
**Interfaces — Produces:** `load_kline(symbol,start,end,cfg=None)->list[{datetime,open,high,low,close,volume,vt_symbol}]`;`GET /kline?symbol&start&end`
|
||||
|
||||
- [ ] **Step 1: 写失败测试**(mock read_db_daily 返回假 BarData → records)
|
||||
- [ ] **Step 2: 验证失败** → FAIL
|
||||
- [ ] **Step 3: 实现 kline.load_kline**(read_db_daily → list[dict])+ 路由
|
||||
- [ ] **Step 4: 验证通过** → PASS
|
||||
- [ ] **Step 5: 提交** — `git commit -am "feat(api): K线接口 /kline"`
|
||||
|
||||
---
|
||||
|
||||
## Task S1.6:前端 回测-新建页
|
||||
|
||||
**Files:** Create: `src/api/{strategy,backtest}.ts`, `src/views/backtest/New.vue`
|
||||
|
||||
- [ ] **Step 1: api/strategy.ts**(getStrategies/getParams);**api/backtest.ts**(submitCta)
|
||||
- [ ] **Step 2: New.vue** — 策略 el-select(onchange 拉参数渲染动态 el-form)+ symbol + el-date-picker 区间 + 提交 → push `/backtest/progress/:id`
|
||||
- [ ] **Step 3: `npm run build`** 验证
|
||||
- [ ] **Step 4: 提交** — `git commit -am "feat(frontend): 回测-新建页"`
|
||||
|
||||
---
|
||||
|
||||
## Task S1.7:前端 回测-进度页 + useTask
|
||||
|
||||
**Files:** Create: `src/composables/useTask.ts`, `src/views/backtest/Progress.vue`
|
||||
|
||||
- [ ] **Step 1: useTask.ts** — 轮询 GET /task/{id}(2s) + WS /ws/task/{id}?token=;导出 {status,stage};done→resolve
|
||||
- [ ] **Step 2: Progress.vue** — 状态徽标 + 阶段文字 + 进度条;done→push result;failed→ElMessage error_msg
|
||||
- [ ] **Step 3: build** 验证
|
||||
- [ ] **Step 4: 提交** — `git commit -am "feat(frontend): 回测-进度页(WS 实时阶段)"`
|
||||
|
||||
---
|
||||
|
||||
## Task S1.8:前端 回测-结果页(ECharts,vnpy client 对齐)
|
||||
|
||||
**Files:** Create: `src/components/charts/{EquityChart,DailyPnlChart,KlineChart}.vue`, `src/components/TradesTable.vue`, `src/views/backtest/Result.vue`
|
||||
|
||||
- [ ] **Step 1: EquityChart**(折线 date×balance);**DailyPnlChart**(柱状红绿);**KlineChart**(candlestick + markPoint 成交买卖点)
|
||||
- [ ] **Step 2: TradesTable.vue**(el-table)
|
||||
- [ ] **Step 3: Result.vue** — 并行拉 result/equity-curve/daily-pnl/trades/kline → 统计卡片 + 图表/表布局
|
||||
- [ ] **Step 4: build** 验证
|
||||
- [ ] **Step 5: 提交** — `git commit -am "feat(frontend): 回测-结果页(对齐 vnpy client)"`
|
||||
|
||||
---
|
||||
|
||||
## Task S1.9:S1 部署 + 端到端冒烟
|
||||
|
||||
- [ ] **Step 1: scripts/smoke_phase3b.py** — 登录→POST /backtest/cta(DoubleMaStrategy,600000,2024-01-01..06-30)→轮询 done→校验 equity-curve/daily-pnl/trades/kline 非空
|
||||
- [ ] **Step 2: 容器跑冒烟** `ssh sanguo-nas "$DOCKER exec sanguo_vnpy_v2 python /app/scripts/smoke_phase3b.py"`
|
||||
- [ ] **Step 3: 同步前端 dist + 后端 → restart**
|
||||
- [ ] **Step 4: 公网验收** — 浏览器跑 DoubleMaStrategy 看完整结果页
|
||||
- [ ] **Step 5: 提交** — `git commit -am "test(phase3b): S1 端到端冒烟"`
|
||||
|
||||
---
|
||||
|
||||
# 切片 S2:投研核心
|
||||
|
||||
## Task S2.1:factor 列表 + ic-summary + report 接口(含 factor 结果持久化)
|
||||
|
||||
**Files:** Modify: `routes.py`, `sanguo_factor/analyzer.py`, `sanguo_orchestrator/runner.py`;Create: `sanguo_api/factor_registry.py`;Test: `tests/api/test_factor_routes.py`
|
||||
|
||||
**Interfaces — Produces:** `GET /factor/list`;`GET /task/{id}/ic-summary`;`GET /task/{id}/report/{factor}`(FileResponse HTML)
|
||||
|
||||
> ⚠️ **实现注意(factor 结果持久化)**:factor worker 返回 `FactorReport`(非 BacktestResult),当前 orchestrator 对其无持久化。S2.1 补:analyzer 把 `{ic_summary, report_paths}` 写 `output_dir/<task_id>_summary.json`;orchestrator 在 `_pending[task_id]` 记 output_dir(submit_factor 已有);`/ic-summary` 与 `/report/{factor}` 从 output_dir 读。task_id 需稳定(当前 `factor_{id(factor_names)}` 不稳定,改含 symbols+时间戳哈希)。
|
||||
|
||||
- [ ] **Step 1: 写失败测试**(FakeOrch 暴露 get_factor_summary(tid)->dict;测 /factor/list、/ic-summary、/report 非空)
|
||||
- [ ] **Step 2: 验证失败** → FAIL
|
||||
- [ ] **Step 3: factor_registry.py**(枚举 sanguo_factor.registry);analyzer 落 summary.json;runner factor task_id 稳定化 + 暴露 output_dir
|
||||
- [ ] **Step 4: 加路由** /factor/list、/task/{id}/ic-summary、/task/{id}/report/{factor}
|
||||
- [ ] **Step 5: 验证通过** → PASS
|
||||
- [ ] **Step 6: 提交** — `git commit -am "feat(api): 投研接口(factor list + ic-summary + tears 报告服务)"`
|
||||
|
||||
---
|
||||
|
||||
## Task S2.2:前端 投研-新建 + 结果页
|
||||
|
||||
**Files:** Create: `src/api/factor.ts`, `src/views/factor/{New,Result}.vue`
|
||||
|
||||
- [ ] **Step 1: api/factor.ts**(getFactors/submitFactor/getIcSummary/reportUrl)
|
||||
- [ ] **Step 2: New.vue** — 因子多选 + 多标的 tag 输入 + 日期 → 提交 → 进度(useTask)→ 结果
|
||||
- [ ] **Step 3: Result.vue** — IC 表(period × mean/std/icir/t_stat/count)+ tears iframe
|
||||
- [ ] **Step 4: build** 验证
|
||||
- [ ] **Step 5: 提交** — `git commit -am "feat(frontend): 投研-新建/结果页(IC 表 + tears)"`
|
||||
|
||||
## Task S2.3:S2 部署 + 冒烟
|
||||
- [ ] 冒烟(ma5,[600000,000001,300750])→ ic-summary/report 非空 → 公网验收 → 提交
|
||||
|
||||
---
|
||||
|
||||
# 切片 S3:优化 + 收尾
|
||||
|
||||
## Task S3.1:回测可配置费率/滑点/资金
|
||||
- [ ] schemas CtaBacktestRequest 加 `rate: float=0.001, slippage: float=0, capital: float=1_000_000`;routes 透传;cta_engine 用参数(替换硬编码);测试;提交
|
||||
|
||||
## Task S3.2:优化结果 + 任务列表接口
|
||||
- [ ] `GET /task/{id}/optimization-results`({results:[{params,statistics}]});`GET /task?type=&status=`(list_results);测试;提交
|
||||
|
||||
## Task S3.3:前端 优化页(热力图)+ 历史页
|
||||
- [ ] Optimize.vue(参数网格表单 + ECharts heatmap);History.vue(任务表 + 回看);build;提交
|
||||
|
||||
## Task S3.4:S3 部署 + 全量冒烟 + 收尾
|
||||
- [ ] 全链路冒烟(回测+优化+因子)→ 公网验收 → 更新 nas-deploy-plan.md(uvicorn 目标)→ 最终提交 → finishing-a-development-branch
|
||||
|
||||
---
|
||||
|
||||
## Self-Review(计划自检)
|
||||
|
||||
1. **Spec 覆盖**:§6 页面 → S0.5/S1.6-1.8/S2.2/S3.3 ✓;§8.2 接口 → S1.3-1.5/S2.1/S3.2 ✓;§10 切片验收 → 每片末 ✓;result_id bug(实现发现)→ S1.1 ✓;factor 持久化缺口(实现发现)→ S2.1 ✓。
|
||||
2. **占位符**:S3 为切片级任务(按 writing-plans scope-check,每片可独立成 plan,到达时按 S0/S1 粒度细化)。无 TBD/TODO 散落。
|
||||
3. **类型一致**:`build_app(config_path, static_dir)`、`list_strategies()`、`strategy_params(name)`、`load_kline(...)`、`_df_to_records` 跨任务一致 ✓。
|
||||
4. **兜底**:vnpy_ctastrategy 本地不可导入(STRATEGY_NAMES);read_db_daily cfg(默认 None);rsync 不稳(ssh-exec)。
|
||||
|
||||
## Execution Handoff
|
||||
|
||||
用户已睡 + /goal 自主完成 → **Inline Execution(superpowers:executing-plans)**:本会话按任务顺序执行,后端 TDD(先红后绿)、前端 build 验证、切片末部署 + 公网验收,频繁提交,不阻塞等用户。
|
||||
Reference in New Issue
Block a user