Files
qlib/docs/ARCHITECTURE.md
Simon 82240e383d docs: 同步操作说明/架构/路线图(归档操作、图表基座、数据库目标硬约束)
- USAGE 新增 §6.3.1「归档的日常操作」:查看 / 筛选(回写 URL)/ 导出完整 JSON /
  以此参数再跑 / 删除(写明「删除即失去结果,结果只存归档一份」)/
  历史归档用 restore_experiment_from_job 按原 id 重建;并说明归档完整度如何标注
- USAGE/README 更正技术栈与图表基座:TradingView Lightweight Charts 4.2.3 为唯一
  图表基座(ECharts 已从 package.json、pnpm-lock.yaml、node_modules、文档与
  架构图标注中全部清除),并记录实测证据(个股页图表根节点为
  div.tv-lightweight-charts,页面 canvas 无一来自其它图表库)
- ARCHITECTURE 更正「当前数据库」(原写 SQLite/未来 MySQL):现为本机 MariaDB 10.11,
  §6 补目标库硬约束;USAGE 补服务器身份与实测连接证据
- AGENT.md 新增 §0.1:数据库目标只允许本机 MariaDB,禁止 192.168.1.10,
  由 config.py::assert_db_target_allowed 硬拦截(命中直接抛错,不静默降级)
- DEV_PLAN_DIVIDEND_BACKTEST 记录三轮实施与验证、归档恢复边界(§12.5)、已知限制
- DEV_PLAN v2/v3 标注为历史记录(避免把当时的远端库地址当现状照抄);
  ROADMAP 更正 M3 图表选型
2026-09-20 07:31:20 +08:00

1042 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# A股个人量化选股与回测平台架构
> 版本:v1.0
> 定位:个人开发、A股、中低频选股/回测、AI Agent 二次开发
> 数据源优先级:Tushare > 新浪财经
> 当前数据库:**本机 MariaDB 10.11(`127.0.0.1:3306/qlib`)**
> 数据库目标约束:**只允许本机**;`192.168.1.10` 已禁止作为 DB 目标,由
> `app/core/config.py::assert_db_target_allowed` 在解析配置时硬拦截(见 `AGENT.md` §0.1)
> 历史:SQLite(`data/quant.db`)→ 远端 MySQL → 本机 MariaDB(逐表一致副本)
> 核心量化引擎:Qlib
> 前端:React / Next.js
> 后端:FastAPI + Python
---
## 1. 项目目标
本项目不是简单给 Qlib 套一个 Web UI,而是构建一个独立的个人量化研究平台:
```text
Web 前端
↓
FastAPI
↓
Research Specification
↓
业务服务层
├── 数据服务
├── 股票池服务
├── 因子服务
├── 选股服务
├── 模型服务
├── 回测服务
└── 实验服务
↓
Qlib / ML
↓
标准化研究结果
↓
SQLite / Parquet
↓
Web 前端
```
AI Agent 作为独立能力层,通过受控 Tool 调用上述服务,而不是直接操作数据库或随意修改 Qlib 内部代码。
---
# 2. 总体架构
```text
┌──────────────────────┐
│ React / Next │
│ │
│ Dashboard │
│ 股票池 │
│ 因子研究 │
│ 选股 │
│ 回测 │
│ Experiment │
│ AI Research │
└──────────┬───────────┘
│
REST / SSE
│
▼
┌──────────────────────┐
│ FastAPI │
│ │
│ API / Auth / DTO │
│ Job / Validation │
└──────────┬───────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
Data Service Research Agent Service
│ Service │
│ │ │
▼ ▼ ▼
DAO / Repo Qlib / ML Agent Tools
│ │ │
└────────────┼──────────────┘
▼
┌──────────────────────┐
│ Persistence │
│ │
│ SQLite │
│ Parquet │
│ Qlib Dataset │
└──────────────────────┘
```
---
# 3. 核心设计原则
## 3.1 Qlib 不是系统数据库
Qlib 负责:
- Feature / Dataset
- 模型训练
- Prediction
- Portfolio
- Backtest
- 部分分析工具
Qlib 不作为业务系统唯一数据存储。
系统数据分为:
```text
业务数据 → SQLite
历史/分析型大数据 → Parquet
Qlib计算数据 → Qlib Dataset
实验结果 → SQLite
```
## 3.2 前端不能直接调用 Qlib
必须经过:
```text
Frontend
↓
FastAPI
↓
Application Service
↓
Qlib Adapter
↓
Qlib
```
这样未来可以替换 Qlib,而不影响前端。
## 3.3 DAO 必须与数据库实现解耦
业务代码禁止直接:
```python
sqlite3.connect(...)
```
也禁止在 Service 中写 SQL。
必须:
```text
Service
↓
Repository / DAO Interface
↓
SQLite Repository
```
未来:
```text
Service
↓
Repository Interface
├── SQLite Repository
└── MySQL Repository
```
业务层不感知底层数据库。
---
# 4. 数据源架构
## 4.1 数据源优先级
```text
Data Service
│
┌────────┴────────┐
▼ ▼
Tushare Sina
Primary Secondary
│ │
└────────┬────────┘
▼
Normalization
│
Validation
│
Storage
```
### 第一优先级:Tushare
用于:
- 股票基本信息
- 日线行情
- 复权因子
- 指数
- 财务数据
- 分红送转
- 停复牌
- 行业/概念
- 其他可用数据
### 第二优先级:新浪财经
主要作为:
- Tushare 数据缺失补充
- 行情数据校验
- 特定实时/准实时数据补充
新浪数据必须经过统一 Adapter,禁止业务代码直接调用新浪接口。
---
# 5. 数据采集流水线
```text
Scheduler
↓
Data Source Adapter
↓
Raw Response
↓
Schema Validation
↓
Normalization
↓
Deduplication
↓
Business Validation
↓
DAO
↓
SQLite
↓
Parquet Export
↓
Qlib Dataset
```
每个数据源都必须有独立 Adapter:
```text
data/
├── sources/
│ ├── base.py
│ ├── tushare.py
│ └── sina.py
├── normalizers/
├── validators/
└── service.py
```
业务层只依赖:
```python
MarketDataProvider
```
而不是:
```python
TushareClient
```
---
# 6. 数据库设计
> **目标库约束(硬性)**:一律 `127.0.0.1:3306/qlib`(本机 MariaDB 10.11)。
> `192.168.1.10` 禁止作为数据库目标 —— 连错库不会报错、界面也正常,却会把回测/归档/策略
> 静默写到另一台机器上,属于最难发现的一类故障。守卫在 `get_settings()` 里执行:
> 命中禁用主机直接抛错(应用起不来),禁用列表可用 `QLIB_FORBIDDEN_DB_HOSTS` 覆盖。
> 启动日志会打印 `数据库目标:mysql qlib@127.0.0.1:3306/qlib`(不含密码),便于随时确认。
## 6.1 SQLite 定位
SQLite 用于:
- 股票基础信息
- 数据源状态
- 数据同步记录
- 因子定义
- 策略定义
- 回测配置
- Experiment metadata
- Job
- 用户配置
- 系统配置
SQLite 不建议长期承载超大规模原始行情明细。
历史行情和大量 Feature 优先使用 Parquet。
---
# 7. DAO / Repository 设计
推荐使用:
```text
SQLAlchemy 2.x
+
Repository Pattern
+
Pydantic DTO
```
目录:
```text
backend/
├── domain/
│ ├── entities/
│ └── repositories/
│
├── infrastructure/
│ └── persistence/
│ ├── sqlalchemy/
│ │ ├── models/
│ │ ├── repositories/
│ │ └── session.py
│ └── migrations/
│
└── application/
└── services/
```
Repository 接口示例:
```python
class StockRepository(Protocol):
def get_by_symbol(self, symbol: str) -> Stock | None: ...
def list(self, filters: StockFilter) -> list[Stock]: ...
def save(self, stock: Stock) -> Stock: ...
```
SQLite:
```python
class SqlAlchemyStockRepository(StockRepository):
...
```
未来 MySQL:
```python
class MySqlStockRepository(StockRepository):
...
```
推荐实际上让两者都基于 SQLAlchemy,而不是分别维护两套 SQL。
这样数据库切换主要通过:
```text
DATABASE_URL
```
完成。
例如:
```text
SQLite:
sqlite:///./data/quant.db
MySQL:
mysql+pymysql://user:password@host/quant
```
业务 Service 不需要修改。
---
# 8. 建议的核心数据库表
第一阶段至少包括:
```text
stock
stock_daily
stock_adjust_factor
industry
stock_industry
index
index_daily
trading_calendar
suspend_data # ⚠️ 未实现(停牌以「当日无行情」近似,结果页如实标注)
stock_st_data # → 由 stock_name_history(tushare namechange)落地时点 ST 判定
financial_indicator
income_statement
# ---- 后续增量(已落地,见 docs/DEV_PLAN_DIVIDEND_BACKTEST.md §10)----
daily_basic # 每日指标:dv_ratio/dv_ttm 股息率、PE/PB、市值(高股息选股)
stock_name_history # 名称生效区间:exclude_st 的**时点**口径(股息陷阱可见)
balance_sheet
cashflow_statement
factor_definition
factor_test
strategy
strategy_factor
backtest
backtest_trade
backtest_position
backtest_metric
experiment
experiment_artifact
job
data_sync_log
```
注意:
`stock_daily` 等大量时序数据如果规模变大,应逐渐迁移到:
```text
Parquet
```
SQLite 保留元数据和索引。
---
# 9. 时间与未来函数防护
所有研究数据必须明确:
```text
trade_date
report_date
announce_date
effective_date
```
财务数据必须按照 `announce_date` 控制可见性。
禁止:
```text
使用未来公告的财务数据回测过去
```
所有数据查询必须明确:
```text
as_of_date
```
例如:
```python
get_financial_data(
symbol="600519.SH",
as_of_date="2024-06-30"
)
```
只允许返回当时市场已经知道的数据。
---
# 10. Domain 层
建议核心领域对象:
```text
Stock
Universe
Factor
FactorTest
Model
Strategy
Portfolio
Backtest
Experiment
Job
```
例如:
```text
Universe
↓
Factor
↓
Model
↓
Strategy
↓
Backtest
↓
Experiment
```
---
# 11. Research Specification
前端、AI Agent 和后端统一使用自己的研究描述对象。
示例:
```json
{
"type": "backtest",
"universe": {
"market": "CN_A",
"exclude_st": true,
"exclude_suspended": true,
"min_listing_days": 250
},
"factors": [
{
"name": "momentum_60",
"weight": 0.3
},
{
"name": "roe_ttm",
"weight": 0.3
},
{
"name": "low_volatility_60",
"weight": 0.4
}
],
"selection": {
"top_n": 30
},
"rebalance": "monthly",
"period": {
"start": "2015-01-01",
"end": "2026-08-31"
}
}
```
然后:
```text
Research Specification
↓
Validator
↓
Strategy Builder
↓
Qlib Adapter
```
---
# 12. 前端结构
第一阶段(**已实现**,侧栏按研究闭环分组):
```text
研究 总览 / 股票池 / 股票筛选
策略 策略库 / 选股回测 / 实验对比
因子与信号 因子研究 / 因子组合 / 交易信号
数据 数据同步 / 数据管理
```
## 12.1 表现层约定(本轮定型)
| 约定 | 取值 | 理由 |
|---|---|---|
| 图表库 | **TradingView Lightweight Charts**(唯一) | 轻量(~45KB)、原生支持「在 series 上打买卖点标记」,金融图表交互(十字光标/缩放)开箱可用;ECharts 已下线(依赖已移除),避免两套图表基座带来的样式与交互分裂 |
| 图表基座 | `components/charts/LwChart.tsx` | 折线/面积/柱状 + 标记 + tooltip + 可点击图例由一个组件承载;**chart 实例只在结构变化时重建**,数据变化只 `setData`,避免每次刷新重建 canvas |
| 主题 | `components/charts/theme.ts` | 颜色/数值格式集中定义(涨绿跌红按 A 股习惯),组件内不出现硬编码色值 |
| 标记约束 | 标记时间必须存在于对应 series 且**升序** | Lightweight Charts 硬约束:不满足会整组标记丢失。`LwChart` 内统一做「过滤到已有时间点 + 排序」,页面只负责给语义(BUY/SELL) |
| 股票标识 | `SymbolLink`(`lib/symbols.tsx`) | 「有代码必有名称」是产品要求:后端填充 `name`(权威),前端 `GET /api/stocks/names` 缓存兜底;名称缺失显示「—」,**不猜不造**(§7 无静默行为) |
| 参数模型 | `components/StrategyParamsForm.tsx` 单一 `StrategyParams` | 策略库/回测/直通入口共用同一参数模型与校验,避免三处各自维护字段(§6 依赖抽象) |
| 策略说明 | `StrategyDoc`(后端 `describe_strategy` 推导) | 说明与公式**由 spec 真实推导**而非前端手写模板:参数一改,公式同步变;引擎未建模处进 `warnings` 如实暴露(§7/§24) |
| 结果视图 | `components/BacktestResultView.tsx`(回测页与归档页**共用**) | 「刚跑完」和「翻回来看」必须是同一套图表与表格;两处各写一套必然漂移成「归档里少一张图」 |
| 归档页 | `/experiments/{id}` 为 **Server Component**(`app/experiments/[id]/page.tsx`) | 归档是只读内容:选股条件/执行依据/元数据必须服务端直出(客户端渲染时 SSR HTML 里连「选股条件」都搜不到);交互(删除/导出/折叠 spec)隔离在 `components/ArchiveActions.tsx` |
| 归档口径说明 | 归档页顶部「选股条件」+「交易执行依据」两块,取自**归档内 spec** | 说明必须与那次执行一致,不能读当前页面状态(否则「看归档」会看到今天的参数);字段逐项对应引擎实执行语义,不写泛泛的模板话 |
| 归档列表分页 | body 保持 `list[...]`,总数放 `X-Total-Count` 响应头 | 不破坏既有前端契约,同时消除「硬编码 limit=50 静默截断」(§7):前端据此显示「显示 N 条 / 共 M 条」 |
> 前端**只**依赖 `/api/*` 的 Domain 结构(§3.2):`name` 由后端填充而不是前端反查数据库,
> 图表库可替换而不影响领域层,`/backtest` 的三种入口(策略/选股/实验)都只是把 URL 参数
> 映射成同一个 `StrategyParams`。
第二阶段:
```text
模型研究
Portfolio
AI Research
数据管理
```
---
# 13. 前端输入
## 股票池
支持:
- 市场
- 股票类型
- 行业
- 市值
- 流动性
- 上市时间
- ST
- 停牌
- 指数成分
## 因子
支持:
- 内置因子
- 自定义表达式
- 因子组合
- 权重
- 标准化
- 中性化
## 选股
支持:
- Top N
- Top %
- Score
- 等权
- Score 加权
- 行业约束
- 单股权重上限
## 回测
支持:
- 起止日期
- 调仓频率
- 初始资金
- 手续费
- 印花税
- 滑点
- 涨跌停限制
- 停牌限制
---
# 14. 前端输出
回测结果统一转换为:
```text
BacktestResult
├── summary
├── equity_curve
├── drawdown
├── monthly_returns
├── yearly_returns
├── positions
├── trades
├── turnover
├── risk_metrics
└── factor_exposure
```
这样前端完全不需要理解 Qlib 内部对象。
---
# 15. 异步 Job
所有耗时任务必须异步:
```text
POST /api/backtests
↓
Job Created
↓
Worker
↓
Qlib
↓
Result
```
前端通过:
```text
SSE
```
接收:
```text
queued
running
factor_calculation
model_training
backtesting
analysis
completed
failed
```
第一阶段可以使用:
```text
FastAPI BackgroundTasks
```
或简单本地 Worker。
系统复杂后再引入:
```text
Redis + Celery/RQ
```
不要第一版过度工程化。
---
# 16. Qlib Adapter
Qlib 必须被封装:
```text
quant/
├── qlib_adapter/
│ ├── dataset.py
│ ├── feature.py
│ ├── model.py
│ ├── backtest.py
│ └── provider.py
```
业务代码:
```python
backtest_service.run(spec)
```
而不是:
```python
qlib.init(...)
qlib.workflow(...)
```
散落在项目各处。
---
# 17. AI Agent 架构
Agent 只能调用 Tool:
```text
Agent
├── search_stocks
├── inspect_factor
├── test_factor
├── create_strategy
├── run_backtest
├── compare_experiments
├── get_backtest_result
└── create_experiment
```
Agent 禁止:
```text
直接 SQL
直接修改数据库
直接删除数据
直接执行任意 shell
直接修改生产策略
```
Agent:
```text
自然语言
↓
Research Plan
↓
Tool Calls
↓
Research Specification
↓
执行
↓
Experiment
↓
分析
```
---
# 18. AI Research 示例
用户:
> 帮我寻找适合A股月度调仓的低频选股因子。
Agent:
```text
1. 获取A股股票池
2. 创建候选因子
3. IC测试
4. RankIC测试
5. 分层测试
6. 相关性分析
7. 删除冗余因子
8. 组合因子
9. Walk Forward
10. 回测
11. 保存 Experiment
12. 输出结论
```
Agent 不能因为一次回测表现好就直接认为策略有效。
必须关注:
```text
样本外
Walk Forward
不同市场阶段
交易成本
换手率
因子稳定性
```
---
# 19. 推荐的开发目录
```text
quant-platform/
│
├── frontend/
│ └── web/
│
├── backend/
│ ├── app/
│ │ ├── api/
│ │ ├── application/
│ │ ├── domain/
│ │ ├── infrastructure/
│ │ ├── quant/
│ │ ├── agent/
│ │ └── core/
│ │
│ └── tests/
│
├── data/
│ ├── raw/
│ ├── normalized/
│ ├── parquet/
│ └── qlib/
│
├── experiments/
│
├── scripts/
│
├── docker/
│
├── docs/
│
├── ARCHITECTURE.md
├── AGENT.md
└── README.md
```
---
# 20. 第一阶段 MVP
不要一开始做所有功能。
### Phase 1:数据
```text
Tushare
↓
标准化
↓
SQLite
↓
Parquet
```
完成:
- 股票列表
- 交易日历
- 日线
- 复权因子
- 基本财务指标
### Phase 2:Qlib
```text
Parquet
↓
Qlib Dataset
↓
Alpha158 / 自定义因子
↓
LightGBM
↓
Backtest
```
### Phase 3:Web
完成:
```text
股票池
因子
选股
回测
结果
```
### Phase 4:Experiment
所有研究自动保存。
### Phase 5:AI Agent
最后再接:
```text
自然语言
↓
Research Plan
↓
Tool
↓
Experiment
```
---
# 21. 数据流总图
```text
Tushare
│
▼
Tushare Adapter
│
│ 失败/缺失
▼
Sina Adapter
│
▼
Raw Data
│
▼
Data Validator
│
▼
Normalization
│
┌─────┴──────┐
▼ ▼
SQLite Parquet
│ │
│ ▼
│ Qlib Dataset
│ │
│ ┌─────┴─────┐
│ ▼ ▼
│ Factor Model
│ │ │
│ └─────┬─────┘
│ ▼
│ Strategy
│ │
│ ▼
│ Backtest
│ │
└────────────┤
▼
Experiment
│
┌─────────┴─────────┐
▼ ▼
Web Output AI Analysis
```
---
# 22. 最终原则
这个系统必须坚持:
1. **Tushare 第一,新浪第二**
2. **数据源通过 Adapter 隔离**
3. **DAO / Repository 隔离数据库**
4. **SQLite 当前使用,SQLAlchemy 保证未来 MySQL 可切换**
5. **大量历史时序数据逐渐转 Parquet**
6. **Qlib 是计算引擎,不是整个系统**
7. **前端只面对自己的 Domain API**
8. **前后端通过 Research Specification 解耦**
9. **所有耗时任务异步化**
10. **所有研究产生 Experiment**
11. **严格防止未来函数**
12. **AI Agent 只能通过 Tool 使用研究能力**
13. **第一阶段不做实盘**
14. **第一阶段不做复杂分布式架构**
15. **每个模块都必须可以被单元测试**
最终目标不是:
> Qlib + Web
而是:
> **一个以 Qlib 为量化研究引擎、以 Tushare 为主要数据源、具备清晰 DAO 抽象和 AI Agent 接口的个人 A 股量化研究平台。**