第三章:数据获取与预处理
各位同学,欢迎来到实战环节的第一步。说实话,很多做量化交易的朋友,模型搞得花里胡哨,最后死在哪?死在数据上。数据不干净,再牛的算法也是白搭。今天我们就来聊聊碳配额价格预测的「地基工程」——数据获取与预处理。
3.1 Python环境搭建:别让工具拖后腿
我个人习惯用 Anaconda 来管理环境。为什么?省心。你想想看,搞数据科学的人,最烦的就是装包报错。Anaconda 帮你把 numpy、pandas、scikit-learn 这些基础库都打包好了。
我建议你专门为碳交易项目建一个虚拟环境:
conda create -n carbon_trading python=3.9
conda activate carbon_trading
pip install pandas numpy matplotlib seaborn scikit-learn statsmodels
嗯,这里要注意。statsmodels 这个库做时间序列分析特别好用,后面我们做 ARIMA 模型时会频繁用到。我在项目中遇到过因为没装这个库,临时补装结果版本冲突的尴尬事。所以,一次性装齐,别偷懒。
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名
3.2 数据源选择:CEA 和 EUA 的门道
做碳配额价格预测,数据源就两个主流:中国碳配额(CEA)和欧盟碳配额(EUA)。两者差别很大,我分别说说。
3.2.1 CEA(中国碳配额)
CEA 的数据获取相对麻烦。目前国内有 8 个试点碳市场,加上全国碳市场,数据分散得很。我个人常用的渠道有:
- 上海环境能源交易所:全国碳市场的交易数据在这里公布
- 湖北碳排放权交易中心:历史数据比较全
- Wind 金融终端:如果你有机构账号,这里的数据最干净
CEA 的数据频率一般是日频,偶尔有周频。注意,国内碳市场早期流动性很差,经常出现连续几天零成交的情况。这个后面处理缺失值时会讲到。
3.2.2 EUA(欧盟碳配额)
EUA 的数据就好拿多了。ICE(洲际交易所)是主要交易场所,数据公开透明。我建议直接从以下渠道获取:
- ICE 官网:提供历史结算价,免费下载
- Investing.com:可以导出 CSV,很方便
- Quandl / FRED:如果你用 Python,可以直接调 API
EUA 的数据质量很高,流动性好,几乎不会出现连续零成交。但要注意,EUA 有期货和现货之分,我们做价格预测一般用主力期货合约的结算价。
3.3 缺失值处理:别让空值毁了你的模型
数据拿到手,第一件事就是检查缺失值。我见过太多人,数据一拉就开始跑模型,结果跑出来一堆 NaN。为什么会这样?因为很多模型(比如 LSTM)不接受缺失值。
先看看数据长什么样:
import pandas as pd
df = pd.read_csv('EUA_data.csv', parse_dates=['Date'], index_col='Date')
print(df.isnull().sum()) # 检查每列的缺失值数量
print(df.info()) # 查看数据类型和缺失情况
处理缺失值,我一般按这个优先级来:
- 删除法:如果缺失比例小于 5%,直接删掉那几行。简单粗暴,但有效。
- 前向填充:用上一个非空值填充。适合价格数据,因为价格不会突变。代码:
df.fillna(method='ffill', inplace=True) - 插值法:用前后值的线性插值。适合时间序列。代码:
df.interpolate(method='linear', inplace=True) - 模型预测:如果缺失值太多(比如超过 20%),我会用 KNN 或随机森林来预测缺失值。但说实话,这种情况很少见。
3.4 异常值检测:揪出那些「离谱」的数据点
异常值,说白了就是那些明显不符合规律的数据点。比如 EUA 价格突然从 50 欧元跳到 500 欧元,那大概率是数据录入错误。
我常用的检测方法有三种:
3.4.1 3σ 原则
假设数据服从正态分布,超出均值 ±3 倍标准差的值就是异常。代码很简单:
mean = df['Close'].mean()
std = df['Close'].std()
df['is_outlier'] = (df['Close'] > mean + 3*std) | (df['Close'] < mean - 3*std)
这个方法适合 EUA,因为 EUA 价格波动相对平稳。但 CEA 早期波动剧烈,用 3σ 可能会误删很多正常数据。
3.4.2 IQR 四分位距法
这个方法更稳健,不受极端值影响。计算 Q1(25%分位数)和 Q3(75%分位数),IQR = Q3 - Q1。超出 [Q1 - 1.5*IQR, Q3 + 1.5*IQR] 的就是异常。
Q1 = df['Close'].quantile(0.25)
Q3 = df['Close'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df['is_outlier'] = (df['Close'] < lower_bound) | (df['Close'] > upper_bound)
我个人更推荐 IQR 法,尤其是处理 CEA 数据时。你想想看,CEA 早期价格从 20 元涨到 60 元,用 3σ 可能把 60 元判成异常,但那是正常的价格发现过程。
3.4.3 可视化辅助
别光靠代码,画个箱线图或者散点图,一眼就能看出问题:
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
df.boxplot(column='Close')
plt.title('箱线图 - 异常值一目了然')
plt.subplot(1, 2, 2)
plt.plot(df.index, df['Close'], 'b-', alpha=0.7)
plt.scatter(df.index[df['is_outlier']], df['Close'][df['is_outlier']], color='red', s=50)
plt.title('时间序列 - 红色点为异常值')
plt.tight_layout()
plt.show()
3.5 本章知识体系总览
下面这张图,把数据获取与预处理的整个流程串起来了。你可以把它当作一个检查清单,每次拿到新数据,按这个流程走一遍,基本不会出大问题。
好了,数据获取和预处理这部分就讲到这里。记住一句话:数据质量决定了模型的天花板。你后面花再多时间调参,也弥补不了数据上的缺陷。所以,在这个阶段多花点功夫,绝对值得。
公众号:蓝海资料掘金营,微信deep3321