Data Layer
属于Qlib
的Infrastructure
(基层)部分,Data Layer提供了大量
API接口,方便user管理(manage)和检索(retrieve)自己的数据。数据装载好后依据实际需求和任务目标收益率搭建模型进行回测或实盘进行下单操作,同时需要进行风控和指标分析。
Data Layer主要包含:
-
Data Preparation(数据准备)
-
Data API(数据接口)
-
Data Loader(数据加载)
-
Dataset(数据集)
-
Cache(缓存)
-
Data and Cache File Structure(数据与缓存文件格式)
一般的数据准备流程是:
首先用官方或自己下载的数据,通过dump_bin.py文件将数据转换为Qlib
支持的格式(.bin)。通常转换后的数据只包含一些基础列(比如OHLCV,即open、high、low、close和volume)。
利用Qlib
内置的Experssion Engine
来自定义其他特征(eg. "Ref($close, 60)/$close",前60个交易日的回报),支持的表达式可以在路径.\qlib\qlib\data\ops.py找到。通常情况这个步骤在Data Handler
类的Data Loader
中实现。
如果要进行更复杂的数据处理,Qlib
同样支持自定义Processor(过程流)
来完成更复杂的操作,路径.\qlib\qlib\data\dataset\processor.py。与上面提到的Experssion Engine
不同的是Processor
提供复杂的、难以使用表达式来处理的数据处理方法(eg. Dropna)。
最后,Dataset
负责把Data Handler
处理好的model-specific
的数据传递给模型。
官方在介绍数据框架时仅简单介绍了默认数据集,一般我们需要用质量更高,频率更高频的数据去进行数据分析和预测建模,官方传送门:数据层:数据框架和用法 — QLib 0.9.5.99 文档,所以我们需要自定义一些数据集。
自定义的数据集原始数据要求满足以下条件:
-
数据文件格式为
.csv
-
数据文件中至少包含一列时间索引(且列名为date)和一列特征(如close)
官方demo中使用的是股票bar数据,这里用自定义的minute自定义数据集,目前我在研究期货交易,这里就用期货分时数据:
import sys, os
from tqdm import tqdm
import pandas as pd, numpy as np
from data_api import au as au # 自己封装的数据API接口
symbols=['SN2410']
class CFG:
start_date = '2024-07-31 00:00:00'
end_date = '2024-08-03 00:00:00'
update_start_date = '2024-07-31 00:00:00'
update_end_date = '2024-08-03 00:00:00'
upd_data_path = './SN2410_1min_upd'
raw_data_path = './SN2410_1min' # 原始csv数据存放位置
target_path = './SN2410_1min_bin' # .bin数据存放位置
if not os.path.exists(CFG.raw_data_path):
os.mkdir(CFG.raw_data_path)
for i in symbols:
try:
data = au.futures_zh_minute_sina(symbol=i, period="1")
except:
break
data.to_csv(f'{CFG.raw_data_path}/{i}.csv', index=False)
下载好的数据:
接着使用官方格式转换脚本dump_bin.py
将.csv
转换为.bin
文件,有几个比较重要的参数:
--csv_path: 原始csv数据存放地址
--qlib_dir: .bin数据存放地址,即格式化后想存放的位置
--freq: 数据频率,可以选择5min或者day
--date_field_name: 表示csv中时间索引的列的列名
--include_fields: 除了时间索引列(date)之外,想要转换的列
--exclude_fields: 除了时间索引列(date)之外,不想转换的列
将上面csv转换:
python dump_bin.py dump_all --csv_path C:\Users\59980\qlib\qlib\workflow\SN2410_1min\SN2410.csv --qlib_dir C:\Users\59980\.qlib\qlib_data\my_data --freq 5min --date_field_name datetime --include_fields close
结果是可以看到转换后的文件的。
更新自定义数据集
后续如果需要对已有的数据集更新同样只需要使用dump_bin.py
脚本进行更新,首先同样要获取所有新合约的历史分时数据,更新数据需要验证几个问题:
-
当更新数据时间范围与原有数据交叉时,是否能自行判断并衔接数据(日期重叠看是否能有效剔除重复数据)
-
能否新增数据(除了SN2410,增加SN2409合约数据)
-
能否只更新以前的一部分数据
我们使用下列代码获取更新数据:
python dump_bin.py dump_update --csv_path ./crypto_1min_upd --qlib_dir ./crypto_1min_bin --freq 5min --date_field_name date --include_fields close
比较简单就跳过这部分。但自定义数据集会遇到数据不完整问题,由于我们获取的数据接口有可能产生数据缺失,下载的时候可能并不全面,而dump_bin.py
脚本在处理时并不会发现这一点问题,因为它只会根据它dump的第一个.csv
文件生成Calendar
,而之后所有文件都公用第一个文件的Calendar
。这一点很关键。
因为这会导致一个问题:对于缺失某一时刻但与Calendar中缺失的时间戳不同的时间序列数据,在读取时可能会发生如下情况:
也就是说,我们的时间轴对应的数据从缺失的那一时刻开始就全部错乱了。故在使用dump_bin.py
脚本生成数据集时,一定要检查数据是否完整或所有.csv
文件的数据条目是否相同。为了填补缺失的数据,考虑使用如下代码进行数据填补:
def fill_csv(csv:pd.DataFrame):
loss_list = []
date = list(csv.date.map(au.datetime_timestamp))
pre = date[0] - 60000
for idx, d in enumerate(date):
if d - pre != 60000:
loss_list.append(idx)
pre = d
data_list = list(csv.values)
for fix, idx in enumerate(loss_list):
add_item = data_list[idx+fix].copy()
add_item[0] = cu.timestamp_2_datetime(au.datetime_timestamp(add_item[0]) - 60000)
data_list.insert(idx+fix, add_item) # 使用后一时刻填补
return pd.DataFrame(data_list, columns=csv.columns)
for csv in os.listdir('./SN2410_upd/'):
fill_csv(pd.read_csv('./SN2410_upd/'+csv)).to_csv('./SN2410_upd/'+csv, index=None)
for csv in os.listdir('./SN2410/'):
fill_csv(pd.read_csv('./SN2410/'+csv)).to_csv('./SN2410/'+csv, index=None)
填补对齐后基本就没有问题了。