- README:架构分层示意(Selection/Signal/Portfolio/Composite Engine)、MySQL 默认配置、 核心能力表、里程碑 M0–M8 - USAGE:概览模块与页面清单、引擎分层 §6.0、API 表新增 /api/selections /api/signals /api/strategies /api/composites、Agent 10 工具、已知限制(M8.4 延后/全市场选股同步耗时)
371 lines
19 KiB
Markdown
371 lines
19 KiB
Markdown
# qlib-platform 使用说明
|
||
|
||
> 适用代码版本:M0–M5 + M-DB + **M6 选股系统 / M7 因子层 / M8(Signal·Portfolio·Strategy·Agent·Web)**
|
||
> 全部完成(数据库已切换 MySQL);若文档与代码不一致,以代码与
|
||
> [ROADMAP.md](./ROADMAP.md) / [DEV_PLAN_v2.md](./DEV_PLAN_v2.md) 为准。
|
||
>
|
||
> 本文覆盖:安装配置、数据同步、启动前后端、研究 API、研究引擎与 Qlib 接入、
|
||
> AI Agent、测试门禁、已知限制。
|
||
|
||
---
|
||
|
||
## 1. 项目概览(当前完成度)
|
||
|
||
个人 A 股中低频量化研究平台:
|
||
|
||
| 模块 | 位置 / 说明 |
|
||
|---|---|
|
||
| 数据层 | `backend/app/domain`、`infrastructure/data_sources`:Tushare 首选 + 新浪备用(Failover 审计);**默认 MySQL**(`config.yaml database.mysql`)+ 日线 Parquet 导出 |
|
||
| 选股系统 | `quant/selection.py` + `application/services/selection_service.py`:条件选股(A)/ 因子评分 TopN(B),`as_of` 当前/历史一致;结果落库可复现、可解释 |
|
||
| 因子层 | `factor_definition` 入库(`/api/factors` 读库)+ Composite Engine(`quant/composite.py`,组合落库 `/api/composites`)+ 行情口径显式化(`price_adjustment`) |
|
||
| 交易信号 | `quant/signal.py`:评分排名 + 趋势规则 → BUY/WATCH/SELL + 理由,落库 `/api/signals` |
|
||
| 研究/回测 | `backend/app/quant`:ResearchSpec → 因子 → IC/RankIC/分层 → TopK 低频回测 → 标准化 `BacktestResult`;**双引擎**:LocalEngine(默认)与 QlibEngine v1 |
|
||
| 策略 | `strategy` 落库 + `/api/strategies`(命名策略,可展开为回测 spec) |
|
||
| Web | `frontend/web`:总览 / 股票池 / **股票筛选** / 因子研究 / 因子组合 / **交易信号** / 选股回测 / 实验 |
|
||
| 异步与归档 | Job 状态机 + Experiment 自动归档 + 一键复跑 + SSE 进度 |
|
||
| AI Agent | 10 个受控工具 + LLM 编排(需配置模型 Key) |
|
||
|
||
**技术栈**:Python 3.12(uv) · FastAPI · SQLAlchemy 2 · Alembic · **MySQL(pymysql)**(SQLite 兜底)· pandas · pyarrow(Parquet) · pyqlib 0.9.8.dev32(源码安装) · LightGBM · Next.js 15 · ECharts · Redis(本机 127.0.0.1:6379 已就绪,触发时接入)
|
||
|
||
---
|
||
|
||
## 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.0 引擎分层(M6–M8)
|
||
|
||
研究/选股链路自 M6 起分层:`Composite Engine`(因子复合分)→ `Selection Engine`
|
||
(条件/评分选股)→ `Signal Engine`(买卖信号)→ `Portfolio`(组合权重)→ 回测。
|
||
**当前选股与历史回测共用同一评分引擎**(`quant/composite.build_score_panel`),
|
||
保证 v2 §25「历史回测与当前选股用同一套逻辑」(一致性由测试锁定)。
|
||
|
||
- 选股:`POST /api/selections`,body 为 `SelectionQuery`(universe / factors / top_n /
|
||
as_of / method);结果候选含 `factor_values` 与 `selection_reason`(为什么选它)。
|
||
- 信号:`POST /api/signals`,body `{query, rules}`;rules 含买入排名阈值/趋势 MA/卖出区间。
|
||
- 策略:`POST /api/strategies` 保存命名策略 → `POST /{id}/expand` 补 period 展开为 spec 提交回测。
|
||
|
||
### 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` | 因子目录(读 `factor_definition` 表;空表自动 seed) |
|
||
| `POST /api/composites` / `GET /api/composites` | 因子组合保存 / 列表(方向由注册表填充) |
|
||
| `POST /api/selections` | 执行选股(`method=score` 评分 TopN / `condition` 条件)→ `SelectionResult` 并落库 |
|
||
| `GET /api/selections/{id}` / `GET /api/selections` | 读回 / 历史选股(`as_of`、`method` 过滤) |
|
||
| `POST /api/signals` | 生成 BUY/WATCH/SELL 信号(评分+规则)→ 落库 |
|
||
| `GET /api/signals/{id}` / `GET /api/signals` | 读回 / 历史信号 |
|
||
| `POST /api/strategies` / `GET /api/strategies` | 保存 / 列表命名策略 |
|
||
| `POST /api/strategies/{id}/expand` | 展开为回测 ResearchSpec(period + 初始资金) |
|
||
| `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 能力边界(10 个内置受控工具,只读 + 受控写库):
|
||
|
||
- `search_stocks` / `get_market_data`:查询
|
||
- `test_factor` / `run_backtest`:研究并**自动归档 Experiment**
|
||
- `screen_stocks` / `explain_selection`:按因子评分选股(传 `symbols` 白名单避免全市场长任务)/ 解释某次选股理由
|
||
- `generate_signals`:生成 BUY/WATCH/SELL 信号
|
||
- `create_strategy`:保存命名策略
|
||
- `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 # 全量测试(每个里程碑提交前均须通过)
|
||
|
||
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. **Qlib 模型选股(Selection 模式 C)未实现**:当前选股为条件(A)/因子评分(B);
|
||
模型预测选股(Alpha158 + LightGBM walk-forward,M8.4)为「按需」延后项
|
||
(见 docs/DEV_PLAN_v2.md §6.4)。
|
||
2. **全市场选股为同步请求**:无白名单的全市场选股约需 60s+;Agent 工具要求传 `symbols`
|
||
白名单,全市场请在 Web 页执行(后续可迁异步 Job)。
|
||
3. **数据规模**:当前 MySQL 已同步全市场(stock 5556 只、stock_daily 约 780 万行、
|
||
adjust_factor 约 790 万行,2026-09 由 SQLite 迁移并校验一致)。
|
||
4. **回测为近似建模**:涨跌停按收盘相对上一有效收盘判定、成交假设调仓日收盘,
|
||
Portfolio v1 仅等权(单股/行业上限约束字段已预留但未建模,设置后会在结果
|
||
`unimplemented` 如实标注)(详见结果 `unimplemented`)。
|
||
5. **Agent 结论质量取决于模型**:编排只保证「经受控工具 + 归档留痕」,研究有效性判断
|
||
需要人复核;接不同厂商模型请核对 `config.yaml` 的 `base_url` 与 `model` 命名。
|
||
6. **SSE 与 Job 为单进程本地执行**:重启进程后未完成 Job 需重新提交(第一阶段刻意
|
||
不引入 Redis/Celery;本机 Redis 已就绪,出现排队/长任务需求时按 DEV_PLAN §7 接入)。
|
||
7. **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 已隔离方言差异)。
|