1. 为什么需要将CSV转换为bin格式
做量化研究的朋友们应该都深有体会,处理海量股票数据时最头疼的就是IO性能问题。我刚开始做量化时,曾经用CSV格式存储了A股市场10年的日线数据,每次跑策略回测都要等上好几分钟,简直让人崩溃。后来接触到Qlib的bin格式,读取速度直接提升了10倍不止,这才明白数据格式的选择有多重要。
CSV作为文本格式虽然直观易懂,但存在三个致命缺陷:一是解析效率低,每次读取都需要进行字符串到数值的转换;二是存储冗余,文本格式会占用更多磁盘空间;三是缺乏标准化,不同来源的CSV字段定义可能千差万别。而Qlib采用的bin格式是二进制存储,不仅读取速度快,还能保持数据类型的精确性。
举个例子,同样是存储1000万条行情数据:
- CSV格式可能需要2GB空间,读取耗时约15秒
- bin格式可能只需要500MB空间,读取仅需1秒左右
2. 准备CSV格式的股票数据
2.1 数据获取方式
我推荐使用Python的efinance包来获取股票数据,这是我用过最稳定的免费数据源之一。安装很简单:
pip install efinance获取单只股票历史数据的完整代码示例:
import pandas as pd import efinance as ef def get_stock_data(code, start="20200101", end="20230101"): df = ef.stock.get_quote_history( stock_code=code, beg=start, end=end, fqt=0 # 0不复权,1前复权,2后复权 ) # 保留核心字段并重命名 df = df.iloc[:, :9] df.columns = ['name', 'code', 'date', 'open', 'close', 'high', 'low', 'volume', 'turnover'] # 删除不必要的列 return df.drop(['name', 'code'], axis=1) # 获取贵州茅台数据 df_600519 = get_stock_data('600519') df_600519.to_csv("600519.csv", index=False)2.2 CSV文件规范要求
Qlib对CSV文件有严格的格式要求,我整理了几个关键点:
- 单文件单股票原则:每个CSV文件只包含一只股票的数据
- 命名规范:文件名必须与股票代码一致,如"600519.csv"
- 必须包含date字段:日期格式推荐YYYY-MM-DD
- 字段顺序:建议按open,close,high,low,volume,turnover排列
一个合规的CSV文件示例:
date,open,close,high,low,volume,turnover 2020-01-02,1128.0,1130.0,1145.06,1116.0,148099,16696837120.0 2020-01-03,1117.0,1078.56,1117.0,1076.9,130319,14266380544.03. CSV转bin格式的两种方法
3.1 命令行方式(推荐新手)
Qlib提供了现成的转换脚本,位于qlib/scripts/dump_bin.py。最常用的命令格式:
python dump_bin.py dump_all \ --csv_path ./csv_data \ --qlib_dir ./qlib_data \ --include_fields open,close,high,low,volume,turnover参数说明:
- csv_path:CSV文件所在目录
- qlib_dir:输出的bin文件目录
- include_fields:需要转换的字段
我建议首次转换时加上--freq day参数明确指定数据频率,避免后续使用出现问题。
3.2 代码调用方式(适合批量处理)
如果需要处理大量股票或者集成到自动化流程中,可以直接调用转换类:
from qlib.tools.dump_bin import DumpDataAll converter = DumpDataAll( csv_path="./csv_data", qlib_dir="./qlib_data", include_fields="open,close,high,low,volume,turnover", freq="day" ) converter.dump()实际项目中,我通常会封装一个批量处理的函数,加入异常处理和日志记录:
import logging from pathlib import Path def batch_convert(csv_dir, qlib_dir): csv_dir = Path(csv_dir) failed = [] for csv_file in csv_dir.glob("*.csv"): try: # 转换逻辑 logging.info(f"Processing {csv_file.name}") except Exception as e: logging.error(f"Failed to process {csv_file.name}: {str(e)}") failed.append(csv_file.name) return failed4. Qlib数据目录结构解析
转换完成后,qlib_dir目录会生成以下结构:
qlib_data/ ├── calendars/ │ └── day.txt ├── features/ │ ├── 600519/ │ │ ├── open.day.bin │ │ ├── close.day.bin │ │ └── ... └── instruments/ └── all.txt4.1 交易日历(calendars)
day.txt文件记录了所有有效交易日,格式如下:
20200102 20200103 20200106 ...这个文件非常重要,Qlib会用它来对齐不同股票的数据时间轴。如果发现数据对不齐的问题,首先应该检查日历文件。
4.2 特征数据(features)
每个股票一个子目录,每个字段一个bin文件。bin文件采用小端浮点格式存储,前4字节是该交易日历中的索引位置,后面是连续的数据值。
可以使用numpy直接读取验证:
import numpy as np data = np.fromfile("qlib_data/features/600519/close.day.bin", dtype="<f") print(data[:10]) # 打印前10个数据4.3 标的信息(instruments)
all.txt文件记录了所有股票的基本信息,格式为:
600519<TAB>20200102<TAB>20230101 600000<TAB>20200102<TAB>20230101 ...5. 在Qlib中使用转换后的数据
5.1 初始化数据环境
使用前需要先初始化Qlib:
import qlib from qlib.constant import REG_CN qlib.init(provider_uri="./qlib_data", region=REG_CN)5.2 基础数据查询
获取交易日历:
from qlib.data import D # 获取2020年所有交易日 calendar = D.calendar(start_time="2020-01-01", end_time="2020-12-31") print(calendar[:5]) # 打印前5个交易日查询股票行情:
# 获取贵州茅台2020年1月的OHLC数据 data = D.features( instruments=["600519"], fields=["$open", "$close", "$high", "$low"], start_time="2020-01-01", end_time="2020-01-31" ) print(data.head())5.3 高级查询技巧
Qlib支持丰富的表达式查询,比如计算5日均线:
data = D.features( instruments=["600519"], fields=["$close", "Mean($close, 5)"], # 计算5日均线 start_time="2020-01-01", end_time="2020-03-31" )6. 常见问题与解决方案
6.1 日期格式不匹配
错误现象:转换后的数据无法正确查询 解决方法:确保所有CSV文件的日期格式统一为YYYY-MM-DD
6.2 字段缺失
错误现象:提示某些字段不存在 解决方法:检查--include_fields参数是否包含所有需要的字段
6.3 数据量太大内存不足
解决方法:
- 分批次转换:使用--limit_nums参数限制每次处理的文件数
- 增加swap空间(Linux系统):
sudo fallocate -l 16G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile6.4 增量更新数据
对于新增数据,使用update模式:
python dump_bin.py dump_update \ --csv_path ./new_data \ --qlib_dir ./qlib_data7. 性能优化建议
- 使用SSD存储:机械硬盘的随机读写性能会成为瓶颈
- 启用并行处理:
python dump_bin.py dump_all ... --max_workers 8- 压缩存储:转换完成后可以使用zstd压缩qlib_data目录
- 内存映射:查询大数据集时使用D.features(..., disk_cache=1)启用磁盘缓存
我在实际项目中的经验是,经过优化后,处理全市场10年日线数据(约20GB CSV)可以在30分钟内完成转换,后续查询都能在秒级响应。