Files
qlib/docs/USAGE.md
T
Simon 0ffd574f30 docs: 下阶段开发计划(架构 v2 落地 M6-M8)+ MySQL 迁移文档同步
- docs/DEV_PLAN_v2.md:基于 ARCHITECTURE_v2 与 M0-M5 现状的下一阶段计划
  (M-DB 迁移收尾 → M6 因子定义入库/复合因子/口径修复 → M7 Selection/Signal/
  Portfolio 引擎分层 → M8 Strategy 平台化/Web 做实/Agent 工具补齐;含本机 Redis
  127.0.0.1:6379 的接入触发点与执行顺序)
- ROADMAP.md:登记 M-DB 里程碑并指向 DEV_PLAN_v2
- USAGE.md / README.md:数据库描述由 SQLite 更新为 MySQL(config.yaml database.mysql)
2026-09-08 23:58:51 +08:00

343 lines
16 KiB
Markdown
Raw 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.
# qlib-platform 使用说明
> 适用代码版本:M1–M5 全部完成,含 QlibEngine v1(Qlib 数据管线)与 Parquet 导出
> (HEAD ≈ `fc12ba8`);若文档与代码不一致,以代码与
> [ARCHITECTURE.md](./ARCHITECTURE.md) / [ROADMAP.md](./ROADMAP.md) 为准。
>
> 本文覆盖:安装配置、数据同步、启动前后端、研究 API、研究引擎与 Qlib 接入、
> AI Agent、测试门禁、已知限制。
---
## 1. 项目概览(当前完成度)
个人 A 股中低频量化研究平台:
| 模块 | 位置 / 说明 |
|---|---|
| 数据层 | `backend/app/domain`、`infrastructure/data_sources`:Tushare 首选 + 新浪备用(Failover 审计);SQLite 存储 + 日线 Parquet 导出(未来 MySQL 可切换) |
| 研究引擎 | `backend/app/quant`:ResearchSpec → 因子 → IC/RankIC/分层 → TopK 低频回测 → 标准化 `BacktestResult`;**双引擎**:LocalEngine(默认,纯 pandas)与 QlibEngine v1(QLibDataset 数据管线) |
| Qlib 接入 | `quant/qlib_adapter/`:SQLite 行情按官方二进制格式落盘 → `D.features` 读取 → 回测(详见 §6.1 与 docs/QLIB_VERIFICATION.md) |
| Web | `frontend/web`:Next.js(TS)+ECharts —— 总览 / 股票池 / 因子研究 / 回测 / 实验 |
| 异步与归档 | Job 状态机 + Experiment 自动归档 + 一键复跑 + SSE 进度 |
| AI Agent | 受控工具白名单 + LLM 编排(需配置模型 Key) |
**技术栈**:Python 3.12(uv) · FastAPI · SQLAlchemy 2 · Alembic · SQLite · pandas · pyarrow(Parquet) · pyqlib 0.9.8.dev32(源码安装) · LightGBM · Next.js 15 · ECharts
---
## 2. 环境准备
```bash
# 工具(本机已装则跳过)
python3 -m pip install uv
# Node 18+ 与 pnpm: https://pnpm.io/installation
# 1) 后端依赖(backend/.python-version 固定 3.12)
cd backend
uv sync # 基础依赖(含 Qlib/qlib 源码安装依赖)
uv sync --extra datasource-tushare # 数据同步需要 Tushare SDK(首次)
```
前端:
```bash
cd frontend/web
pnpm install # 依赖使用 npmmirror 镜像时较快
cp .env.local.example .env.local # NEXT_PUBLIC_API_BASE 默认 http://127.0.0.1:8000/api
```
> Qlib 安装说明:本机(Linux aarch64 + CPython 3.12)经源码 git 安装并固定 commit,
> 见 `backend/pyproject.toml` 与 [docs/QLIB_VERIFICATION.md](./QLIB_VERIFICATION.md)。
---
## 3. 配置
### 3.1 根目录 `.env`(密钥,不入库)
```bash
cp .env.example .env
```
| 变量 | 必填 | 说明 |
|---|---|---|
| `TUSHARE_TOKEN` | 同步数据时必填 | Tushare Pro token |
| `LLM_API_KEY` | 使用 Agent 时必填 | 大模型 API Key(URL/模型名在 config.yaml) |
| `DATABASE_URL` | 否 | 默认库 = `config.yaml → database.mysql`(MySQL `192.168.1.10/qlib`);设此项可覆盖(如切回 SQLite) |
| `MYSQL_PASSWORD` | 使用 MySQL 默认库时必填 | MySQL 密码(`config.yaml database.mysql.password_env` 引用;host/db/user 在 config.yaml) |
| `APP_SECRET_KEY` | 否 | 应用密钥(未接登录,可暂不改) |
> 约定:**密钥只放 `.env`**;URL、模型名等可配置项放 `config.yaml`。`.env` 已被 gitignore,严禁提交。
### 3.2 根目录 `config.yaml`(可配置项,可入库)
常用字段(节选,完整见仓库根 config.yaml):
```yaml
app: {name, version, debug, secret_key_env}
api: {prefix: "/api"}
database:
url_env: "DATABASE_URL"
migrations_dir: ...
mysql: {enabled: true, host: "192.168.1.10", port: 3306,
db: "qlib", user: "qlib", password_env: "MYSQL_PASSWORD", charset: "utf8mb4"}
# URL 优先级:DATABASE_URL 环境变量 > database.mysql 组装 > sqlite:///./data/quant.db 兜底
data_source: {primary: "tushare", fallback: "sina", tushare_token_env: "TUSHARE_TOKEN"}
storage: {parquet_dir: "data/parquet", qlib_dir: "data/qlib", ...} # 相对项目根
agent:
llm: # ← AI Agent 模型接入
base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1" # URL 在这里配
model: "qwen-plus" # 模型名在这里配
api_key_env: "LLM_API_KEY" # Key 仍从 .env 读
```
- **换模型**:改 `config.yaml → agent.llm.model`(如 `qwen-max` / `deepseek-chat` 等
OpenAI 兼容接口模型),并按平台同步 `base_url`。
- 也可用 `.env` 的 `LLM_BASE_URL` / `LLM_MODEL` 覆盖 yaml(env 优先)。
### 3.3 数据库迁移
```bash
cd backend
uv run alembic upgrade head # 在 config 指定库上建表(默认 MySQL qlib;SQLite 兜底库为 data/quant.db)
# 开发中改 Model 后:
uv run alembic revision --autogenerate -m "desc"
uv run alembic upgrade head
# 对指定库执行(不随默认配置):
DATABASE_URL='mysql+pymysql://user:pass@host/db' uv run alembic upgrade head
```
> 2026-09 已把数据从 SQLite(`data/quant.db`)全量迁移至 MySQL(`192.168.1.10:3306/qlib`),
> 迁移工具与校验见 `scripts/migrate_sqlite_to_mysql.py`(`--verify-only` 可复查一致性)。
---
## 4. 数据同步与导出
```bash
cd backend
uv run python -m app.cli.sync basic # 股票基础信息(全市场)
uv run python -m app.cli.sync calendar --start 20240101 --end 20241231
uv run python -m app.cli.sync daily --symbols 600519.SH,000858.SZ --start 20230101
uv run python -m app.cli.sync daily --all --start 20240101 --resume # 全市场 + 断点续传
uv run python -m app.cli.sync financial --all # 财务指标(默认增量)
uv run python -m app.cli.sync financial --all --full # 财务指标(强制全量重拉)
uv run python -m app.cli.sync verify --symbol 600519.SH # 新浪交叉验证
uv run python -m app.cli.sync export [--years 2023,2024] # 日线按年导出 Parquet
```
- 每次拉取写入 `sync_log` 审计(来源 / 成功与否 / 行数 / 区间),禁止静默切换数据源。
- `daily` 同时写入日线与复权因子;`--resume` 从本地最新交易日续传。`financial`
默认增量:本地已含「最新应披露报告期」(按 A 股披露节奏推算)的股票直接跳过;
逐只落库,中断/限速后重跑同一命令即可续传补齐(只丢当前一只)。
- 新浪兜底带「两边一致」真实性校验:Tushare 报错时,只有当该股票本地历史与新浪
返回数据**重叠部分一致**,才把新浪**新数据**(本地缺失键)导入,并标记
`source=sina`(日线另标记 `adjust=qfq` 前复权;新浪不提供复权因子)。
校验口径:财务为重叠报告期的 eps / 销售毛利率逐期一致(ROE 两端口径不同,
不作依据);日线为最近重叠交易日的前复权价一致(老交易日在除权后不可比)。
本地无历史可对照或校验不一致 → 拒绝导入并告警,留待 Tushare 恢复后重跑补齐。
- 限速时可加 `--sleep 秒数` 加大请求间隔;`financial --full` 重拉全部历史并覆盖既有行。
- 新浪(`verify`)仅交叉验证,返回**前复权**口径,不会并入不复权主库。
- 财务指标带 `announce_date`(公告日)与 `source`(tushare/sina);研究侧只允许
使用已公告数据(防未来函数),对同一报告期优先消费 `source=tushare` 的行。
- `export`:`data/parquet/stock_daily/<year>.parquet`(列:symbol/trade_date/ohlc/volume/amount),
由 pyarrow 写出,可直接用 pandas 读取分析,或作为 Qlib 等引擎的后续数据源。
---
## 5. 启动
**一键脚本(后端 + 前端同时管理)**:
```bash
scripts/dev.sh start # 启动后端(8000) + 前端(3000)
scripts/dev.sh status # 查看状态
scripts/dev.sh restart # 重启
scripts/dev.sh stop # 停止
scripts/dev.sh logs api # 跟踪后端日志(web 同理)
# 局域网访问前端时(例如 http://192.168.1.160:3000):
# BACKEND_API_URL=http://192.168.1.160:8000 scripts/dev.sh start
```
手动分别启动见下:
```bash
# 后端(端口 8000)
cd backend
uv run uvicorn app.main:app --reload --port 8000
# 交互文档:http://127.0.0.1:8000/docs 健康检查:/api/health
# 前端(端口 3000)
cd frontend/web
pnpm dev
# 打开 http://127.0.0.1:3000
```
页面:`总览` → `股票池`(搜索/列表)→ `因子研究`(目录 + 单因子 IC/RankIC/分层)→
`回测`(参数 → 净值/回撤/月度/持仓/未建模标注)→ `实验`(归档列表 / 详情 / 一键复跑)。
---
## 6. 研究 API 与研究引擎
### 6.1 研究引擎:LocalEngine(默认)与 QlibEngine v1
服务通过 `QuantEngine` Protocol 注入引擎(`backend/app/quant/engine.py`):
- **LocalEngine**(默认,研究服务与 API 使用):纯 pandas —— 因子注册表计算 → 截面
z-score 复合 → TopK 月/周调仓回测。无需 Qlib 数据。
- **QlibEngine v1**(`quant/qlib_adapter/engine.py`):同一套因子与记账规则,但行情经
**QlibDataset** 供给 —— 把本地行情按 qlib 官方二进制格式落盘(默认 `data/qlib`,
可注入 `QlibEngine(qlib_dir=...)`)→ `qlib.init` + `D.features` 读取 close → 回测。
切换示例(代码内):
```python
from app.quant.engine import LocalEngine # 默认
from app.quant.qlib_adapter.engine import QlibEngine # Qlib 数据管线引擎
from app.quant.service import ResearchService
engine = LocalEngine() # 或 QlibEngine()
service = ResearchService(stock_repo, daily_repo, engine)
```
注意:Qlib bin 以 float32 存储,QlibEngine 结果与 LocalEngine 存在极小数值差
(真实 20 股对照:-12.81% vs -12.97%),属预期精度差异,不影响结论方向。
### 6.2 API(curl 示例)
研究输入统一为 ResearchSpec(前端 / API / Agent 同构):
```bash
curl -X POST http://127.0.0.1:8000/api/backtests \
-H 'Content-Type: application/json' \
-d '{
"type": "backtest",
"universe": {"exclude_st": true, "min_listing_days": 0},
"factors": [{"name": "momentum_60", "weight": 1.0}],
"selection": {"top_n": 5},
"rebalance": "monthly",
"period": ["2024-03-01", "2024-12-31"]
}'
```
| 端点 | 说明 |
|---|---|
| `GET /api/health` | 健康检查 |
| `GET /api/stocks?q=600519&limit=20` | 股票列表/搜索 |
| `GET /api/stocks/{symbol}` | 单只股票详情 |
| `GET /api/factors` | 因子目录(公式/lookback/方向) |
| `POST /api/factor-tests` | 同步单因子测试 → `FactorTestReport` |
| `POST /api/backtests` | 同步回测 → `BacktestResult`(默认 LocalEngine) |
| `GET /api/backtests/last` | 最近一次同步回测 |
| `POST /api/jobs` | 创建异步 Job(同 spec),返回 `{"job_id","status":"queued"}` |
| `GET /api/jobs/{id}` | 查询状态;成功时内嵌 `result` |
| `GET /api/jobs/{id}/events` | SSE 进度(`curl -N ...`) |
| `GET /api/experiments` | 实验归档列表(收益摘要/代码版本) |
| `GET /api/experiments/{id}` | 实验详情(spec + 完整结果) |
| `POST /api/experiments/{id}/rerun` | 一键复跑 → 新 Job |
| `POST /api/agent/chat` | AI 研究助手(见 §7) |
**防未来函数与真实性**:回测在调仓日收盘成交、自次日起计收益;财务数据只使用
`announce_date` 之前已公告内容;成本(佣金/印花税/滑点)、涨跌停、停牌已建模;
未建模约束(如开盘一字路径)会写入结果的 `unimplemented` 数组,前端如实展示。
---
## 7. AI Agent
前置:`config.yaml → agent.llm`(URL/模型名默认百炼兼容端点 + qwen-plus),
`.env` 中设置 `LLM_API_KEY`(仅需 Key)。
```bash
curl -X POST http://127.0.0.1:8000/api/agent/chat \
-H 'Content-Type: application/json' \
-d '{"message": "帮我测试 momentum_60 因子并跑一次 Top5 月度回测,评估是否值得深入"}'
```
返回:
```json
{
"reply": "(最终结论,中文)",
"actions": [
{"tool": "test_factor", "args": {...}, "output": "因子测试完成(Experiment EXP-…)..."},
{"tool": "run_backtest", "args": {...}, "output": "回测完成(Experiment EXP-…)..."}
]
}
```
Agent 能力边界(内置受控工具,只读):
- `search_stocks` / `get_market_data`:查询
- `test_factor` / `run_backtest`:研究并**自动归档 Experiment**
- `get_experiment` / `compare_experiments`:读取/对比归档
**不提供** shell / 任意代码执行 / 删改数据 / 修改配置与凭证。未配置 Key 时接口返回
400 引导信息。研究纪律写入系统提示:禁止仅凭单次样本内高收益判定策略有效,需说明
样本外、过拟合、look-ahead bias、成本、参数敏感性等(未验证项要明说)。
---
## 8. 测试与质量门禁
```bash
cd backend
uv run ruff check app tests && uv run ruff format --check app tests
uv run pytest # 当前 86 passed
cd frontend/web
pnpm run typecheck
pnpm run build
```
覆盖重点:Provider 归一化与 Failover 审计、Repository 幂等与「未来函数阻断」
(`as_of_date`/`announce_date`)、Alembic 迁移、因子/IC/分层、回测记账与成本/涨跌停、
**QlibEngine 数据管线**(bin 落盘格式/roundtrip/端到端回测)、Job 状态机与 Experiment
归档、Agent 工具白名单与编排、LLM 配置加载、API 端到端。
---
## 9. 已知限制与说明
1. **QlibEngine v1 边界**:数据管线(落盘→D.features→回测)已打通,见
docs/QLIB_VERIFICATION.md;**Alpha158 全特征 + LightGBM walk-forward 预测信号仍未实现**,
当前 QlibEngine 使用共享因子分回测(ROADMAP §2 备注为该增强 TODO)。
2. **数据规模**:仓库自带示例数据为 20 只权重股 2023–2024 日线(`sync --all` 可扩展
全市场,注意耗时与 Tushare 积分限制)。
3. **回测为近似建模**:涨跌停按收盘相对上一有效收盘判定、成交假设调仓日收盘,未建模
开盘一字 / 集合竞价 / 盘中路径(详见结果 `unimplemented`)。
4. **Agent 结论质量取决于模型**:编排只保证「经受控工具 + 归档留痕」,研究有效性判断
需要人复核;接不同厂商模型请核对 `config.yaml` 的 `base_url` 与 `model` 命名。
5. **SSE 与 Job 为单进程本地执行**:重启进程后未完成 Job 需重新提交(第一阶段刻意
不引入 Redis/Celery)。
6. **Qlib 数据为进程内单例初始化**:`qlib.init` 以 provider_uri 为键幂等,切换不同
qlib_dir 会重新初始化;测试环境建议注入独立临时目录。
---
## 10. 常见问题(FAQ)
- **同步报「缺少 TUSHARE_TOKEN」**:把 token 填入根目录 `.env` 的 `TUSHARE_TOKEN`。
- **同步报「未安装 tushare 客户端」**:`cd backend && uv sync --extra datasource-tushare`。
- **Agent 报 400「未配置 LLM」**:`.env` 设置 `LLM_API_KEY`;换模型改 `config.yaml` 的
`agent.llm.model` / `base_url`。
- **回测默认用哪个引擎?怎么切 Qlib 引擎**:默认 LocalEngine(纯 pandas)。需要在代码
中向 `ResearchService` 注入 `QlibEngine()` 启用 QlibDataset 数据管线(见 §6.1)。
- **Parquet 导出文件在哪、怎么读**:`data/parquet/stock_daily/<year>.parquet`;
`pd.read_parquet(...)` 直接读取(pyarrow 已随依赖安装)。
- **前端连不上后端 / NetworkError**:前端默认经 Next **同源代理**访问 `/api/*`
(next.config.ts rewrites → `127.0.0.1:8000`,可用 `BACKEND_API_URL` 覆盖),
任意 IP 访问 `:3000` 都不需要 CORS 或硬编码后端地址;后端 CORS 开发期为 `*`。
若使用 SQLite 兜底库且日志出现 `database is locked`,多半是正在跑全市场数据
同步(长写事务),同步结束后自动恢复(SQLite 连接已加 busy_timeout);
默认 MySQL 库无此问题。
- **数据库被改动想重置**:默认 MySQL(qlib@192.168.1.10)时在远端重建后
`uv run alembic upgrade head`(行情需重新同步或从备份恢复);若切回 SQLite 兜底库则删除
`data/quant.db` 后重建。
- **默认库已是 MySQL**(config.yaml `database.mysql`,密码在 `.env` 的 `MYSQL_PASSWORD`);
**想临时切回 SQLite**:`.env` 设 `DATABASE_URL=sqlite:///./data/quant.db`。
业务层均无需改动(Repository / SQLAlchemy 已隔离方言差异)。