Tortoise/doc/概要设计.md
2026-06-17 15:17:34 +08:00

18 KiB
Raw Permalink Blame History

Tortoise 投研回测系统 概要设计

1. 项目背景与目标

Tortoise 定位为个人/小团队使用的轻量级 A 股投研回测平台。基于 doc/数据源选型.md 中确定的免费、无 Key 的数据组合mootdx + 腾讯财经), 对照 doc/需求.md 的功能清单,向上构建:自选股管理、行情查看、内置策略 回测、AI 短期上涨概率推算的一体化工作台。

核心目标(与 doc/需求.md 对齐):

  • 自选股:增删改查、多分组、当前行情(当日/周/月涨跌、PE/PB、历史 K 线。
  • 策略:内置主要策略,按"选股策略 + 执行参数"运行;结果图表化、可叠加多策略、 自动持久化(同参数复用,参数变更则覆盖)。
  • AI 接入:可配置多模型与提示词,输出短期上涨概率。
  • 暂不开发:用户权限(仅保留扩展位)。

非功能目标:

  • 单机即可部署,前后端同进程,开箱即用。
  • 数据可复现:策略结果与"数据快照版本"绑定。
  • 预留分布式扩展点(任务队列、独立计算节点)。

2. 设计原则

  • B/S 架构:浏览器即客户端,服务端集中调度数据与计算。
  • JS 一体式框架:前后端共享同一个 Node.js 进程与同一套 TS 类型定义。
  • 数据源解耦:以 DataAdapter 抽象隔离 mootdx / 腾讯财经接口,未来可加 akshare、tushare 等。
  • 存储极简:行情/因子/元数据/任务/策略结果全部收敛到 单一 DuckDB 文件 不再引入 SQLite 与独立 Parquet 目录(详见第 5.2 节)。
  • 任务化所有耗时操作拉数据、回测、AI 调用走任务队列UI 实时订阅进度。
  • 可复现:策略代码版本 + 参数 + 数据快照版本号 三者绑定。
  • 遵循 doc/开发规范.md:函数中文注释(功能/参数/返回/注意事项);需求变更 同步更新文档+代码+测试;测试用例使用唯一编号(章-序)。

3. 技术选型

选型 说明
一体式框架 Nuxt 3 (Nitro + Vue 3) 同进程托管 SSR 前端 + server/api REST + WebSocketTS 原生
UI 库 Element Plus + ECharts 5 表格、表单、K 线、净值/概率曲线
状态管理 Pinia 与 Nuxt 3 集成最佳
后端运行时 Node.js ≥ 20Nitro
存储 DuckDB单文件 同一个 tortoise.duckdb 同时承载行情、因子、元数据、任务、策略与 AI 结果;通过 schema 分区(md.* / meta.* / run.*
缓存 进程内 LRU 热点行情/快照短期缓存,避免穿透 DuckDB
任务队列 内置内存队列(默认) / BullMQ + Redis可选 检测到 REDIS_URL 自动切换
数据源桥接 本地 Python sidecarFastAPI + mootdx + Node 端 HTTP腾讯财经 mootdx 仅 Python 实现
AI 接入 OpenAI 兼容协议为主,扩展 Anthropic/本地 Ollama 模型与 Prompt 配置存 DuckDB
策略沙箱 isolated-vm 隔离用户/内置策略代码,禁用 fs/net
测试 Vitest单测/接口) + Playwright端到端 用例编号遵循 章-序
包管理 pnpm
部署 单机 node .output/server/index.mjs 或 Docker Compose含 sidecar

关于 mootdx因仅有 Python 实现Tortoise 用一个常驻 Python FastAPI sidecar 暴露 /kline /snapshot /f10 /finance 等接口Node 端通过 DataAdapter 调用。 腾讯财经字段解析全部在 Node 端实现已校准索引39=PE_TTM、43=振幅、46=PB、52=PE 静态)。

4. 总体架构

┌───────────────────────────────────────────────────────────────┐
│                        Browser (Vue 3 SSR/SPA)                │
│  自选股 │ 行情/K 线 │ 策略运行台 │ 结果对比 │ AI 概率 │ 任务  │
└───────────────▲───────────────────────────▲───────────────────┘
                │ HTTPS / WebSocket         │
┌───────────────┴───────────────────────────┴───────────────────┐
│                Nuxt 3 一体式服务Node.js 20                │
│ ┌──────────┐ ┌────────────┐ ┌─────────────────────────────┐   │
│ │  Pages   │ │ server/api │ │   WebSocket / SSE 推送      │   │
│ └──────────┘ └─────┬──────┘ └──────────────┬──────────────┘   │
│                     │                       │                  │
│ ┌───────────────────┴───────────────────────┴───────────────┐  │
│ │                     Service 层                             │  │
│ │ Watchlist │ Market │ Strategy │ AI │ Job │ Data           │  │
│ └───┬────────┬─────────┬─────────┬─────┬─────────┬──────────┘  │
│     │        │         │         │     │         │             │
│ ┌───┴───┐ ┌──┴──────┐ ┌┴────────┐ ┌┴──┐ ┌──────┴─────────┐     │
│ │Adapter│ │ Indicator│ │Strategy │ │AI │ │  Job Queue     │     │
│ │mootdx │ │ Library  │ │Registry │ │Hub│ │ memory/BullMQ  │     │
│ │腾讯   │ │          │ │(内置)   │ │   │ │                │     │
│ └─┬───┬─┘ └──────────┘ └─────────┘ └─┬─┘ └────────────────┘     │
│   │   │                              │                          │
│   │   └─ HTTP ─► 腾讯财经 qt.gtimg.cn │                          │
│   └──── HTTP ─► Python sidecar (mootdx, FastAPI)               │
│                                                                 │
│ ┌────────────────────────────────────────────────────────────┐  │
│ │   单一存储tortoise.duckdbschema 分区 md/meta/run     │  │
│ └────────────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────────┘

5. 模块划分

5.1 数据采集层DataAdapter

  • MootdxAdapter:通过 sidecar 拉取 K 线category 4=日、5=周、6=月、7=1m…11=60m、 五档、逐笔、季报、F10。内置坑位规避drop(columns=["datetime"])reset_index 屏蔽 block()/minute() 空接口。
  • TencentAdapterHTTP 调 qt.gtimg.cnGBK 解码,按校准后的索引解析 PE/PB/振幅/ 市值/涨跌停。带断言PE/PB 合理性),异常告警。
  • 统一输出标准 DTOKlineSnapshotTickFinanceSnapshotF10Section 共享于 shared/dto/,前后端复用 Zod 校验。

5.2 数据存储层(单一 DuckDB

决策:原方案"DuckDB + SQLite + Parquet"三件套合并为 一个 DuckDB 文件。 理由:

  1. DuckDB 同时支持列存大表(行情/因子)与小事务表(元数据),单连接即可覆盖。
  2. 备份/迁移 = 拷贝单文件,避免三套生命周期不一致。
  3. Parquet 仅作为"导出/外部分析"格式,不再作为运行期主存储;需要时用 COPY ... TO 'xxx.parquet' 一行导出即可。
  4. 单机场景下事务并发压力低DuckDB 的 MVCC 足以承担元数据写入。

DuckDB 内部按 schema 分区,文件名 data/tortoise.duckdb

schema 说明
md (market data) kline_daily(symbol,date,o,h,l,c,vol,amount,adj) 日线,主键 (symbol,date)
kline_min(symbol,ts,period,o,h,l,c,vol) 多周期分钟线period∈{1,5,15,30,60}
snapshot_daily(symbol,date,pe_ttm,pe_static,pb,mcap,float_mcap,turnover,limit_up,limit_down,amplitude) 估值快照(腾讯校准字段)
finance_quarter(symbol,report_date,...37 字段) mootdx 季报快照
f10(symbol,section,content,fetched_at) F10 9 大类文本
meta instruments(symbol,name,exchange,board,listed_at,delisted_at,...) 标的元数据
watchlist(id,symbol,note,created_at) 自选股
watchlist_group(id,name) 分组
watchlist_group_map(watchlist_id,group_id) 多对多映射(满足"一个自选股属多个分组"
data_jobs(id,kind,params,status,error,started_at,finished_at) 数据/策略/AI 任务
ai_providers(id,name,base_url,api_key_enc,model,enabled) AI 模型配置
ai_prompts(id,scene,template,version,active) 提示词模板(按场景版本化)
data_versions(as_of_ts,scope,row_count,checksum) 数据快照版本(供回测/AI 复现)
run strategies(id,code_id,name,kind,params_schema) 内置策略注册表
strategy_runs(id,strategy_id,universe_hash,params_hash,as_of_ts,status,started_at,finished_at) 策略运行记录;同 (strategy_id,universe_hash,params_hash) 复用,参数变化即新建
strategy_curves(run_id,date,equity,position_json) 策略每日净值/持仓
strategy_metrics(run_id,key,value) 年化、回撤、Sharpe 等指标
ai_runs(id,provider_id,prompt_id,symbol,as_of_ts,prob_up,horizon_days,raw_response) AI 概率推算结果

复用规则(对应需求"自动保存策略执行结果"(strategy_id, universe_hash, params_hash, as_of_ts) 作为幂等键,相同键复用历史结果; 参数或选股变化导致 hash 变化 → 新建运行,旧记录保留;同 hash 重跑则覆盖(按需求要求)。

并发与备份:

  • 写入集中:仅 Job Worker 持有可写连接Web 请求走只读连接。
  • 备份:每日定时 EXPORT DATABASE 'backup/yyyymmdd'DuckDB 原生命令,输出 Parquet+SQL。 这是唯一保留的 Parquet 用途——离线备份/外部分析,不参与运行期。

5.3 业务服务层

  • WatchlistService:自选股 CRUD + 分组多对多 + 当前行情聚合(合并 mootdx 当日 + 腾讯快照)。
  • MarketServiceK 线/快照/F10 查询;周/月涨跌由日线即时聚合DuckDB SQL window)。
  • StrategyService
    • 内置策略注册(strategies 表 + 代码集中在 server/engine/strategies/*.ts)。
    • 运行入口:传入 strategyId + 选股策略 + 参数;命中幂等键直接返回历史结果。
    • 多策略对比:同一图表叠加多个 run_idstrategy_curves
  • AIService
    • providers/prompts 管理 API
    • predictUp(symbol, horizon)拼装提示词含最近行情、估值、F10 摘要)→ 调模型 → 解析概率 → 落 ai_runs
    • 提示词模板支持变量:{{symbol}} {{kline_summary}} {{valuation}} {{news}}
  • JobService:任务登记/进度/取消;统一 WebSocket 频道 /_ws/jobs
  • DataService:增量拉取调度、缺口检测、data_versions 写入。

5.4 Web 应用层

页面(与需求一一对应):

路由 对应需求 关键组件
/watchlist 自选股增删改查、分组、当前行情 分组侧栏 + 行情表(涨跌/PE/PB
/quote/:symbol 历史 K 线/财务/F10 ECharts K 线 + 切换周期
/strategy 策略运行台 选择内置策略 → 选股策略 → 参数表单 → 运行
/strategy/compare 多策略叠加 run_id 净值曲线叠加
/ai AI 模型与提示词配置、批量预测 Provider/Prompt 管理 + 概率结果
/jobs 任务监控 列表 + 实时日志
/data 数据更新 触发增量、查看版本

API统一 JSON + Zod 校验):

  • /api/watchlist/*/api/quote/*/api/strategy/*/api/ai/*/api/jobs/*/api/data/*
  • 实时通道:/_ws任务进度、策略增量净值、AI 流式 token 可选)。

5.5 任务调度

  • 默认内存队列;检测到 REDIS_URL 自动切 BullMQ。
  • 内置任务日终增量拉数、F10 月更、策略运行、AI 批量预测、每日备份。
  • cron 表达式存 meta.data_jobskind='cron' 子类)。

6. 关键流程

6.1 自选股查看

  1. 前端 /watchlistmeta.watchlist + watchlist_group_map
  2. WatchlistService.enrich()
    • 当日涨跌、PE/PB → TencentAdapter 实时(带 LRU 缓存 5s
    • 周/月涨跌 → DuckDB 对 md.kline_daily 做 5/20 日窗口聚合。
  3. 返回合并 DTO点击单只 → /quote/:symbolMarketService 拉 K 线。

6.2 策略运行(含自动保存与复用)

  1. 用户在 /strategy 选择内置策略 + 选股策略universe+ 参数。
  2. 服务端计算 params_hash = sha1(json(params))universe_hash = sha1(symbols)as_of_ts = max(md.kline_daily.date)
  3. run.strategy_runs(strategy_id, universe_hash, params_hash, as_of_ts)
    • 命中且状态=success → 直接返回历史 run_id自动复用)。
    • 否则新建 run参数变更 → 新 run覆盖语义通过 UI 保留最新一条同参数 run
  4. Job Worker 拉起策略:从 DuckDB 流式读 bar按事件驱动撮合strategy_curvesstrategy_metrics
  5. WebSocket 推送净值完成跳到结果页ECharts 渲染)。
  6. 多策略对比:在 /strategy/compare 选多条 run_id → 单图叠加曲线。

6.3 AI 短期上涨概率

  1. /ai/predict 选标的、提示词模板、模型 provider、预测窗口如 5 日)。
  2. AIService 拼上下文:最近 60 日 K 线摘要、最新估值、最近一期财报关键指标、F10 摘要。
  3. 调模型OpenAI 兼容/Anthropic/Ollama解析输出概率要求模型按指定 JSON 格式返回)。
  4. run.ai_runs,前端图表展示概率 + 置信区间 + 模型/Prompt 版本。
  5. 支持批量:自选股分组一键预测。

6.4 数据增量更新

  1. 调度器触发 daily-update
  2. DataServicemeta.instruments → 比对 md.kline_daily 最新日期 → 计算缺口。
  3. MootdxAdapter 拉缺口 K 线≤800 根/次自动分页)+ 季报;TencentAdapter 拉当日快照。
  4. 写入 DuckDB事务更新 meta.data_versionsmeta.data_jobs
  5. WebSocket 推送进度。

7. 目录结构(建议)

tortoise/
├── app/                     # Nuxt 前端
│   ├── pages/
│   │   ├── watchlist.vue
│   │   ├── quote/[symbol].vue
│   │   ├── strategy/index.vue
│   │   ├── strategy/compare.vue
│   │   ├── ai.vue
│   │   ├── jobs.vue
│   │   └── data.vue
│   ├── components/
│   └── composables/
├── server/
│   ├── api/                 # REST 端点
│   ├── services/            # Watchlist/Market/Strategy/AI/Job/Data
│   ├── adapters/            # mootdx / tencent
│   ├── engine/
│   │   ├── indicator/       # 指标库
│   │   ├── strategies/      # 内置策略
│   │   └── backtest/        # 撮合/绩效
│   ├── ai/                  # provider/prompt 引擎
│   ├── jobs/                # 队列与定时任务
│   └── db/                  # duckdb 单连接封装、迁移脚本
├── sidecar/                 # Python FastAPI + mootdx
├── shared/                  # 前后端共享 TS 类型 + Zod schema
├── tests/                   # Vitest + Playwright用例编号 章-序)
├── data/                    # tortoise.duckdb + backup/
├── doc/
└── nuxt.config.ts

8. 安全与运维

  • 默认绑定 127.0.0.1,对外发布需走反向代理(用户权限按需求暂不开发,仅预留中间件位)。
  • 数据源限速mootdx 单连接串行 + 退避;腾讯每秒 ≤ 10 请求AI 调用按 provider 配置 RPS。
  • AI Keyai_providers.api_key_enc 用本地随机主密钥 AES-GCM 加密;主密钥放 data/.master.keygitignore
  • 日志pino + 按日切分;任务失败落 meta.data_jobs.error
  • 备份:每日 EXPORT DATABASEdata/backup/yyyymmdd/,保留 N 天。
  • 策略沙箱:isolated-vm 禁用 fs/net仅暴露受控的指标/数据 API。

9. 里程碑(与需求一致的优先级)

阶段 范围 产出
M1 数据底座 DataAdapter + 单 DuckDB + 数据更新页 A 股全市场日线/估值/季报入库
M2 自选股与行情 Watchlist CRUD + 分组 + 当前行情 + K 线页 覆盖需求"自选股"全部条目
M3 策略 内置策略 + 运行台 + 自动保存/复用 + 多策略叠加 覆盖需求"策略"全部条目
M4 AI 接入 Provider/Prompt 管理 + 概率预测 + 批量 覆盖需求"AI 接入"
M5 调度与备份 定时任务 + 备份 + 任务监控完善 长期稳定运行

10. 风险与对策

  • mootdx 海外不可用 → sidecar 必须部署在国内主机UI 给出连通性自检。
  • 腾讯字段索引漂移TencentAdapter 自带断言PE_TTM、PB 范围),异常立即告警。
  • DuckDB 单文件并发 → 写入仅 Job WorkerWeb 请求走只读连接;高频读热表加 LRU。
  • DuckDB 单文件损坏风险 → 每日 EXPORT DATABASE 备份,启动时校验。
  • 策略代码安全isolated-vm 沙箱禁用 fs/net。
  • AI 输出不稳定 → 强制 JSON Schema 输出 + 重试 + 失败留 raw_response 供排查。
  • 数据一致性 / 结果可复现 → 每次写入登记 meta.data_versions,策略与 AI 运行绑定 as_of_ts