Files
2026-05-22 09:34:49 +08:00

307 lines
12 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.
# ashare-data
A股数据抓取工具,股票列表、交易日历使用 AKShare,日线行情使用 BaoStock,其余部分按功能分别使用通达信本地缓存或 AKShare,保存到 MySQL 数据库。
## 功能概览
| 数据类型 | 说明 | 数据来源 |
|---------|------|---------|
| 股票列表 | 沪深A股代码、名称、上市日期 | AKShare |
| 交易日历 | 1990年至今的交易日列表 | AKShare |
| 日线行情 | 开高低收、成交量/额、振幅、涨跌幅、换手率(前复权,限沪深 A 股) | BaoStock 日线接口 |
| 新浪1分钟数据 | 最近 1 分钟K线(开高低收、成交量/额) | AKShare 新浪接口 |
| 指数日线 | 上证/沪深300/中证500/中证1000/科创50/深证成指/创业板指/中小板指 | 通达信本地缓存 |
| 涨跌停统计 | 每日主板(10%)/ 科创创业板(20%)涨跌停数量 | stock_daily 汇总 |
| 概念板块 | 通达信本地概念板块缓存 | 通达信本地缓存 |
**初始化说明**:首次运行 `--stock-info` / `--trading-day` 时会自动从 AKShare 拉取并写入数据库;`--daily` 现在改为 BaoStock 日线接口,只写 `stock_daily``--daily-no-data-only` 仅更新 `stock_no_data``--sector` / `--index` / `--intraday` 仍按通达信本地缓存工作,不再依赖 `hsjday.zip` 这类旧的日线初始化包。
## 快速开始
### 1. 环境要求
- Python >= 3.10
- MySQL >= 8.0
### 2. 安装依赖
```bash
pip install pymysql sqlalchemy pyyaml pandas requests baostock akshare
```
> 说明:当前只需要 `pymysql`、`sqlalchemy`、`pyyaml`、`pandas`、`requests`、`baostock` 等基础依赖。
> 如需使用 `--sina-min1` 命令,还需要 `akshare`。
> 通达信分钟缓存刷新已自动按最多 100 只股票一批拆分。注意通达信客户端原生只支持刷新 `1m/5m` 本地缓存。
> 仅 `--sector` / `--index` / `--intraday` 等命令需要通达信本地目录,默认读取 `config.yaml` 里的 `tdx.dir`。
### 3. 配置数据库
> ⚠️ **安全提示**`config.yaml` 包含数据库密码,**必须**保留在 `.gitignore` 中(本仓库已默认忽略)。请勿在提交前删除此规则。如曾不慎提交真实密码,参见 [TODO.md](./TODO.md) "清理 git 历史中的明文密码"。
复制配置文件并修改 MySQL 连接信息:
```bash
cp config.example.yaml config.yaml
```
编辑 `config.yaml`
```yaml
mysql:
host: "localhost"
port: 3306
user: "root"
password: "your_password"
database: "ashare"
charset: "utf8mb4"
fetch:
delay: 0.1 # 请求间隔(秒)
retry: 2 # 失败重试次数
workers: 1 # 并发线程数
tdx:
dir: "C:\\new_tdx64" # 通达信安装目录
```
确保 MySQL 中已创建对应数据库:
```sql
CREATE DATABASE ashare CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
### 4. 运行
```bash
# 1. 先抓取股票列表(数据来源: AKShare)
python -m src.main --stock-info
# 2. 抓取交易日历(数据来源: AKShare)
python -m src.main --trading-day --start-date 20100101 --end-date 20261231
# 3. 抓取全历史日线行情(数据来源: BaoStock 日线接口,限沪深 A 股)
python -m src.main --daily --start-date 20260521 --end-date 20260521
# 4. 日常增量更新日线(数据来源: BaoStock 日线接口,限沪深 A 股;只写 stock_daily
python -m src.main --daily
# 4.1 仅更新停牌/无数据记录,不写 stock_daily(数据来源: BaoStock 日线接口,限沪深 A 股)
python -m src.main --daily-no-data-only --start-date 20100101 --end-date 20260517
# 5. 下载新浪 1 分钟数据并写入 stock_min1(数据来源: AKShare 新浪接口,默认 2010 至今)
python -m src.main --sina-min1
# 6. 下载新浪 1 分钟数据并指定区间(数据来源: AKShare 新浪接口)
python -m src.main --sina-min1 --start-date 20260501 --end-date 20260517 --symbol 600519
# 7. 抓取概念板块及成分股(数据来源: 通达信本地缓存)
python -m src.main --sector
# 8. 汇总每日涨跌停统计(数据来源: stock_daily 汇总)
python -m src.main --market-daily
```
### 5. 命令行参数说明
```
数据抓取选项:
--stock-info 抓取A股股票列表(数据来源: AKShare)
--trading-day 抓取交易日历(数据来源: AKShare)
--daily 抓取日线行情(数据来源: BaoStock 日线接口;限沪深 A 股;增量,自动分析数据缺口,已完整自动跳过;只写入 stock_daily,不更新 stock_no_data
--daily-no-data-only 仅更新 stock_no_data(数据来源: BaoStock 日线接口;限沪深 A 股;不写入 stock_daily
--sina-min1 抓取新浪 1 分钟数据并写入 stock_min1(数据来源: AKShare 新浪接口;默认全市场、默认 2010 至今,可配合 --symbol / --start-date / --end-date
--sector 抓取概念板块及成分股(数据来源: 通达信本地缓存;增量,本地板块已齐全时跳过)
--market-daily 汇总每日涨跌停统计(数据来源: stock_daily 聚合)
--symbol 指定单只或多个股票代码,逗号分隔;不传则抓取全市场,供 --sina-min1 使用
日期过滤(对日线行情、交易日历、新浪1分钟生效;新浪1分钟默认 2010 至今):
--start-date 开始日期,格式 YYYYMMDD
--end-date 结束日期,格式 YYYYMMDD
```
## 数据库表结构
### stock_info — 股票基本信息
| 字段 | 类型 | 说明 |
|------|------|------|
| code | VARCHAR(10) PK | 股票代码 |
| name | VARCHAR(50) | 股票名称 |
| ipo_date | DATE | 上市日期 |
### stock_daily — 日线行情(前复权)
| 字段 | 类型 | 说明 |
|------|------|------|
| code | VARCHAR(10) | 股票代码 |
| date | DATE | 交易日期 |
| open | FLOAT NOT NULL | 开盘价 |
| close | FLOAT NOT NULL | 收盘价 |
| high | FLOAT NOT NULL | 最高价 |
| low | FLOAT NOT NULL | 最低价 |
| volume | FLOAT NOT NULL | 成交量 |
| turnover | FLOAT NOT NULL | 成交额 |
| amplitude | FLOAT NOT NULL | 振幅% |
| pct_change | FLOAT NOT NULL | 涨跌幅% |
| change | FLOAT NOT NULL | 涨跌额 |
| turnover_rate | FLOAT NOT NULL | 换手率% |
联合主键:`(code, date)`
### trading_day — 交易日历(AKShare
| 字段 | 类型 | 说明 |
|------|------|------|
| date | DATE UQ | 交易日期 |
### stock_no_data — 停牌/无数据记录
| 字段 | 类型 | 说明 |
|------|------|------|
| code | VARCHAR(10) | 股票代码 |
| date | DATE | 停牌/无数据日期 |
联合主键:`(code, date)`
### stock_min5 — 分钟K线
| 字段 | 类型 | 说明 |
|------|------|------|
| code | VARCHAR(10) | 股票代码 |
| datetime | DATETIME | 时间 |
| open | FLOAT | 开盘价 |
| high | FLOAT | 最高价 |
| low | FLOAT | 最低价 |
| close | FLOAT | 收盘价 |
| volume | FLOAT | 成交量 |
| amount | FLOAT | 成交额 |
联合主键:`(code, datetime)`
### stock_min1 — 1分钟K线
| 字段 | 类型 | 说明 |
|------|------|------|
| code | VARCHAR(10) | 股票代码 |
| datetime | DATETIME | 时间 |
| open | FLOAT | 开盘价 |
| high | FLOAT | 最高价 |
| low | FLOAT | 最低价 |
| close | FLOAT | 收盘价 |
| volume | FLOAT | 成交量 |
| amount | FLOAT | 成交额 |
联合主键:`(code, datetime)`
### stock_concept — 概念板块及成分股
| 字段 | 类型 | 说明 |
|------|------|------|
| code | VARCHAR(10) | 股票代码 |
| concept_code | VARCHAR(20) | 概念板块代码 |
| concept_name | VARCHAR(100) | 概念板块名称 |
联合主键:`(code, concept_code)`
### index_daily — 指数日线行情
| 字段 | 类型 | 说明 |
|------|------|------|
| code | VARCHAR(10) | 指数代码 |
| date | DATE | 交易日期 |
| open | FLOAT | 开盘价 |
| high | FLOAT | 最高价 |
| low | FLOAT | 最低价 |
| close | FLOAT | 收盘价 |
| volume | FLOAT | 成交量 |
| amount | FLOAT | 成交额 |
| pct_change | FLOAT | 涨跌幅% |
联合主键:`(code, date)`
### market_daily — 每日涨跌停统计
| 字段 | 类型 | 说明 |
|------|------|------|
| date | DATE PK | 交易日期 |
| limit_up_10 | INT | 涨停数(10% 主板) |
| limit_up_20 | INT | 涨停数(20% 科创板/创业板) |
| limit_down_10 | INT | 跌停数(10% 主板) |
| limit_down_20 | INT | 跌停数(20% 科创板/创业板) |
> 阈值:涨停 `pct_change >= 9.8`(主板)或 `>= 19.5`(20% 板块);跌停反向。当前可通过 `python -m src.main --market-daily` 运行 `src/fetchers/market_daily.py` 提供的 `fetch_market_daily()`。
## 项目结构
```
ashare-data/
├── config.yaml # MySQL 连接配置(.gitignore
├── config.example.yaml # 配置文件模板
├── pyproject.toml # 项目元数据
├── requirements.txt # 运行依赖
├── README.md # 项目说明(当前文档)
├── TODO.md # 已知问题与待办事项
├── src/
│ ├── __init__.py
│ ├── config.py # 配置读取模块(YAML + 环境变量 ASHARE_CONFIG
│ ├── log.py # 统一 logging(控制台 + logs/ashare.log 按日滚动)
│ ├── db.py # SQLAlchemy 模型 + 批量 upsert
│ ├── main.py # 命令行入口
│ └── fetchers/
│ ├── __init__.py
│ ├── stock_list.py # 股票列表(AKShare
│ ├── trading_day.py # 交易日历(AKShare
│ ├── daily.py # 日线行情(BaoStock
│ ├── index.py # 主要指数日线
│ ├── market_daily.py # 每日涨跌停统计(从 stock_daily 聚合)
│ ├── sina_minute.py # 新浪 1 分钟数据(AKShare
│ └── sector.py # 概念板块及成分股(通达信本地)
├── tests/ # pytest 测试(66 个用例,纯函数 + mock)
├── benchmarks/ # 并发度压测脚本(不在 CI 跑,需真实 MySQL+外网)
└── gzl/ # 选股脚本(独立子项目,可选)
├── Selector.py
└── select_stock.py
```
## 设计说明
- **数据源分工**:股票列表、交易日历使用 AKShare;日线行情使用 BaoStock;概念板块、指数日线、分钟线等仍按功能读取通达信本地缓存;新浪 1 分钟数据使用 AKShare 新浪接口。
- **线程安全**:通达信本地客户端调用集中在 `src/fetchers/tdx_client.py`
- **去重写入**:所有表通过 `db.batch_upsert()` 走 MySQL `INSERT ON DUPLICATE KEY UPDATE`,重复执行不会产生重复数据。
- **全量增量**:所有模块均支持增量更新——日线按交易日对比找缺口,概念板块在本地已齐全时跳过;新浪 1 分钟数据支持按日期过滤后落库。
- **停牌识别**:日线行情若两端已覆盖、内部仍有缺口,则视为停牌,不再重抓。
- **表结构初始化**`init_db()` 只负责建表。
- **进程内缓存**`get_stock_codes()` / `get_ipo_dates()` 缓存全量股票代码与上市日期,避免重复扫库。
- **日志**:所有代码通过 `from src.log import get_logger` 输出;控制台 + `logs/ashare.log`(按日滚动,保留 7 天)双输出,通过 `ASHARE_LOG_LEVEL=DEBUG` 切换级别。
## 运行测试
```bash
pip install pytest # 或 pip install -e ".[dev]"
pytest -v
```
- 当前 66 个用例全部通过,覆盖:
- `daily._ak_hist_to_rows` / `daily._fill_derived_fields`
- `daily._fetch_one_stock` BaoStock 日线读取
- `daily.fetch_daily` BaoStock 日线抓取与落库
- `intraday._tdx_market_data_to_rows` / `intraday.fetch_intraday` 通达信分钟抓取
- `market_daily._is_20pct` 主板/创业板/科创板/北交所判定
- `tdx_blocks.load_infoharbor_blocks` 通达信本地板块缓存解析
- `sector._fetch_concept_list` / `sector._fetch_concept_stocks` 通达信本地概念板块解析
- `sector.fetch_sector` 概念板块增量跳过逻辑
- `src.log.get_logger` 命名空间、handler 幂等、env 控制 level
## 性能压测
```bash
python -m benchmarks.bench_daily --codes 200 --days 30 --workers 1,2,4,8
```
会依次以指定的并发档位跑一遍真实抓取并输出 speedup 表,用于确定 `fetch.workers` 的最优档位。详见 [benchmarks/README.md](./benchmarks/README.md)。
## 选股子项目 `gzl/`
`gzl/` 目录是独立的选股策略原型(依赖 `scipy` + 本地 CSV,不读 MySQL)。当前与主项目 `src/` 解耦,**不参与** `python -m src.main` 的任何流程。后续是否合并到 `src/strategies/` 见 [TODO.md](./TODO.md)。