第二章:数据获取与清洗
做碳配额交易量化策略,第一步不是写模型,而是搞定数据。
我见过太多人一上来就搞复杂算法,结果数据源一塌糊涂,回测结果根本没法用。说白了,数据质量决定了策略的天花板。今天我们就拿湖北碳市场开刀,把数据获取和清洗这关彻底打通。
2.1 湖北碳市场数据源分析
湖北碳排放权交易中心是国内最早启动的区域碳市场之一,交易数据相对完整。我个人习惯从以下几个渠道获取历史数据:
- 官方渠道:湖北碳排放权交易中心官网(hbets.cn)的"行情数据"板块
- 第三方聚合:上海环境能源交易所、广州碳排放权交易所的公开数据接口
- 数据服务商:Wind、东方Choice等金融终端(需付费)
这里有个坑——官方数据通常只提供最近一年的完整日频数据,更早的历史数据需要手动拼接。我在做湖北碳市场回测时就吃过这个亏,以为数据齐全,结果2018年的数据缺了整整两个月。
⚠️ 注意:湖北碳市场在2021年7月全国碳市场上线后,交易规则有过调整。获取数据时务必确认时间区间对应的交易机制是否一致。
2.2 数据获取实战:用Python爬取湖北碳市场数据
我们直接上代码。这里用requests库模拟浏览器请求,从湖北碳市场行情页面抓取日度交易数据。
import requests
import pandas as pd
from datetime import datetime, timedelta
import time
def fetch_hubei_carbon_data(start_date, end_date):
"""
获取湖北碳市场日度交易数据
"""
base_url = "https://www.hbets.cn/api/market/data/daily"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://www.hbets.cn/'
}
all_data = []
current_date = datetime.strptime(start_date, '%Y-%m-%d')
end = datetime.strptime(end_date, '%Y-%m-%d')
while current_date <= end:
date_str = current_date.strftime('%Y-%m-%d')
params = {'date': date_str}
try:
resp = requests.get(base_url, params=params, headers=headers, timeout=10)
if resp.status_code == 200:
data = resp.json()
if data['code'] == 0 and data['data']:
all_data.append(data['data'])
print(f"✅ 成功获取 {date_str} 数据")
else:
print(f"⚠️ {date_str} 无数据或非交易日")
else:
print(f"❌ {date_str} 请求失败,状态码:{resp.status_code}")
except Exception as e:
print(f"❌ {date_str} 异常:{str(e)}")
current_date += timedelta(days=1)
time.sleep(0.5) # 礼貌性延迟,别把人家服务器搞崩了
return pd.DataFrame(all_data)
# 获取2023年全年数据
df_raw = fetch_hubei_carbon_data('2023-01-01', '2023-12-31')
print(f"原始数据量:{len(df_raw)} 条")
嗯,这里要注意——湖北碳市场不是每天都有交易。实际交易日大概只占全年工作日的60%左右,所以返回的数据条数会远小于365。
2.3 数据清洗:把脏数据变成干净的时间序列
拿到原始数据后,你会发现字段名可能是中文的,格式也不统一。我一般按以下步骤清洗:
- 字段重命名:把中文列名转成英文,方便后续处理
- 类型转换:日期转datetime,价格和成交量转float
- 排序去重:按日期升序排列,删除重复行
- 异常值过滤:剔除价格为零或负数的记录
def clean_carbon_data(df):
"""
清洗湖北碳市场数据
"""
# 字段映射(根据实际返回字段调整)
column_map = {
'交易日期': 'date',
'开盘价': 'open',
'最高价': 'high',
'最低价': 'low',
'收盘价': 'close',
'成交量': 'volume',
'成交额': 'amount'
}
df = df.rename(columns=column_map)
# 只保留需要的字段
cols = ['date', 'open', 'high', 'low', 'close', 'volume', 'amount']
df = df[cols]
# 类型转换
df['date'] = pd.to_datetime(df['date'])
for col in ['open', 'high', 'low', 'close', 'volume', 'amount']:
df[col] = pd.to_numeric(df[col], errors='coerce')
# 排序去重
df = df.sort_values('date').drop_duplicates(subset='date')
# 异常值过滤
df = df[(df['close'] > 0) & (df['volume'] >= 0)]
# 重置索引
df = df.reset_index(drop=True)
return df
df_clean = clean_carbon_data(df_raw)
print(f"清洗后数据量:{len(df_clean)} 条")
print(df_clean.head())
💡 经验之谈:我在处理湖北数据时发现,2022年6月有一周的数据连续出现"收盘价=开盘价"的情况。后来查证是系统故障导致的重复记录,直接删掉就好。建议清洗后手动抽查几个日期,和官网行情页面对比一下。
2.4 缺失值处理:碳市场数据的特殊之处
碳市场数据有个特点——非交易日不产生数据。这和股票市场不一样,股票市场非交易日直接跳过就行,但碳市场因为交易不活跃,经常出现"有交易日但无成交"的情况。
为什么会这样?说白了,碳配额交易是政策驱动型市场,企业只有在履约期临近时才会密集交易。平时可能一周都没几笔成交。
我建议按以下策略处理缺失值:
| 缺失类型 | 处理方式 | 适用场景 |
|---|---|---|
| 非交易日缺失 | 不填充,保持时间序列不连续 | 日频策略、事件驱动策略 |
| 交易日无成交 | 前向填充(ffill) | 技术指标计算、连续时间序列模型 |
| 连续多日缺失 | 线性插值 | 低频策略、周度/月度模型 |
| 价格异常缺失 | 剔除该交易日 | 所有场景(避免引入噪声) |
def handle_missing_values(df, method='ffill'):
"""
处理缺失值
method: 'ffill' 前向填充, 'linear' 线性插值, 'drop' 直接删除
"""
df = df.copy()
# 先生成完整的日历日期序列
full_date_range = pd.date_range(start=df['date'].min(),
end=df['date'].max(),
freq='D')
df_full = df.set_index('date').reindex(full_date_range)
if method == 'ffill':
# 前向填充:用上一个交易日的数据填充
df_filled = df_full.fillna(method='ffill')
elif method == 'linear':
# 线性插值:适合连续多日缺失
df_filled = df_full.interpolate(method='linear')
elif method == 'drop':
# 直接删除缺失行
df_filled = df_full.dropna()
else:
raise ValueError("不支持的填充方法")
# 重置索引
df_filled = df_filled.reset_index().rename(columns={'index': 'date'})
return df_filled
# 示例:使用前向填充
df_ffill = handle_missing_values(df_clean, method='ffill')
print(f"前向填充后数据量:{len(df_ffill)} 条")
# 示例:使用线性插值
df_linear = handle_missing_values(df_clean, method='linear')
print(f"线性插值后数据量:{len(df_linear)} 条")
📌 核心原则:碳市场数据清洗要遵循"最小干预"原则。不要为了追求数据连续性而过度填充,否则会引入虚假的交易信号。我个人在实盘策略中,对缺失超过5个交易日的数据直接做截断处理。
2.5 数据质量检查清单
数据清洗完成后,我习惯做一轮快速检查,确保数据可用:
- 时间跨度检查:起始日期和结束日期是否覆盖目标区间
- 价格合理性:收盘价是否在合理范围内(湖北碳市场历史价格约20-60元/吨)
- 成交量检查:是否存在连续多日零成交(超过10天需标记)
- 跳空检查:相邻交易日价格波动是否超过20%(可能是数据错误)
- 复权检查:碳市场没有除权除息,但要注意是否有政策调整导致的"价格断层"
我曾经在检查湖北数据时发现,2021年7月16日全国碳市场上线当天,湖北市场的收盘价突然跳涨了15%。后来一查,是因为全国碳市场启动带来的政策利好。这种跳空是真实的,不能当异常值处理。
2.6 本章知识体系
下面这张图把数据获取与清洗的核心流程串起来了:
数据获取与清洗是量化策略的基石。这一步做扎实了,后面的特征工程和模型训练才能站得住脚。记住:宁可数据少一点,也要保证数据真一点。
🔧 实用工具推荐:我常用pandas-profiling库做数据质量报告,一行代码就能生成HTML格式的数据概览,包括缺失值分布、异常值检测、相关性分析等。非常适合数据清洗后的快速验证。