- financial 默认增量:按 A 股披露节奏判断已最新并跳过;--full 强制全量重拉 - Tushare fina_indicator 增加报告期窗口与 100 条/请求自动分页(修复老报告期静默截断) - 新浪兜底收紧为校验兜底:两源重叠历史一致才导入缺失键,行标记 source=sina; 财务可比字段取 eps/销售毛利率(ROE 两端口径不同不作依据),日线只比较最近重叠交易日 - CLI 输出逐只进度与导入内容描述(来源/行数/报告期与公告区间),失败股票留待重跑 - financial_indicator 增 source 列(迁移 d3f6c9a21b04);新增一致性/分页/服务测试
15 KiB
qlib-platform 使用说明
适用代码版本:M1–M5 全部完成,含 QlibEngine v1(Qlib 数据管线)与 Parquet 导出 (HEAD ≈
fc12ba8);若文档与代码不一致,以代码与 ARCHITECTURE.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. 环境准备
# 工具(本机已装则跳过)
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(首次)
前端:
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。
3. 配置
3.1 根目录 .env(密钥,不入库)
cp .env.example .env
| 变量 | 必填 | 说明 |
|---|---|---|
TUSHARE_TOKEN |
同步数据时必填 | Tushare Pro token |
LLM_API_KEY |
使用 Agent 时必填 | 大模型 API Key(URL/模型名在 config.yaml) |
DATABASE_URL |
否 | 留空使用 SQLite <项目根>/data/quant.db |
APP_SECRET_KEY |
否 | 应用密钥(未接登录,可暂不改) |
约定:密钥只放
.env;URL、模型名等可配置项放config.yaml。.env已被 gitignore,严禁提交。
3.2 根目录 config.yaml(可配置项,可入库)
常用字段(节选,完整见仓库根 config.yaml):
app: {name, version, debug, secret_key_env}
api: {prefix: "/api"}
database: {url_env: "DATABASE_URL", echo: false, migrations_dir: ...}
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 数据库迁移
cd backend
uv run alembic upgrade head # 建表(首次会自动建 data/quant.db)
# 开发中改 Model 后:
uv run alembic revision --autogenerate -m "desc"
uv run alembic upgrade head
4. 数据同步与导出
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. 启动
一键脚本(后端 + 前端同时管理):
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
手动分别启动见下:
# 后端(端口 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 → 回测。
切换示例(代码内):
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 同构):
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)。
curl -X POST http://127.0.0.1:8000/api/agent/chat \
-H 'Content-Type: application/json' \
-d '{"message": "帮我测试 momentum_60 因子并跑一次 Top5 月度回测,评估是否值得深入"}'
返回:
{
"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:研究并自动归档 Experimentget_experiment/compare_experiments:读取/对比归档
不提供 shell / 任意代码执行 / 删改数据 / 修改配置与凭证。未配置 Key 时接口返回 400 引导信息。研究纪律写入系统提示:禁止仅凭单次样本内高收益判定策略有效,需说明 样本外、过拟合、look-ahead bias、成本、参数敏感性等(未验证项要明说)。
8. 测试与质量门禁
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. 已知限制与说明
- QlibEngine v1 边界:数据管线(落盘→D.features→回测)已打通,见 docs/QLIB_VERIFICATION.md;Alpha158 全特征 + LightGBM walk-forward 预测信号仍未实现, 当前 QlibEngine 使用共享因子分回测(ROADMAP §2 备注为该增强 TODO)。
- 数据规模:仓库自带示例数据为 20 只权重股 2023–2024 日线(
sync --all可扩展 全市场,注意耗时与 Tushare 积分限制)。 - 回测为近似建模:涨跌停按收盘相对上一有效收盘判定、成交假设调仓日收盘,未建模
开盘一字 / 集合竞价 / 盘中路径(详见结果
unimplemented)。 - Agent 结论质量取决于模型:编排只保证「经受控工具 + 归档留痕」,研究有效性判断
需要人复核;接不同厂商模型请核对
config.yaml的base_url与model命名。 - SSE 与 Job 为单进程本地执行:重启进程后未完成 Job 需重新提交(第一阶段刻意 不引入 Redis/Celery)。
- 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 开发期为*。 若 API 返回 500 且日志出现database is locked,多半是正在跑全市场数据同步 (长写事务),同步结束后自动恢复(引擎已加 busy_timeout 等待)。 - 数据库被改动想重置:删除
data/quant.db后uv run alembic upgrade head重建 表结构(行情需重新同步)。 - 想切换 MySQL:
.env设DATABASE_URL=mysql+pymysql://user:pass@host/db, 业务层无需改动(Repository 已隔离)。