数据获取与清洗:碳期货历史数据来源与实战处理

做量化交易,数据就是你的弹药库。没有干净的数据,再牛的策略也是白搭。今天咱们就聊聊碳期货数据怎么拿、怎么洗。

我个人习惯把数据获取分成两条路:一条是直接从交易所拿,另一条是通过第三方数据商。两条路我都走过,各有各的坑。

碳期货数据来源:ICE vs EEX

全球碳期货交易,说白了就两大阵地:ICE(洲际交易所)和EEX(欧洲能源交易所)。

ICE 是老大,EUA(欧盟配额)期货的主力合约基本都在这里。我当年第一次调ICE的API时,差点被它的认证机制搞疯——后来发现是文档没看仔细。

EEX 呢,欧洲本土的交易所,碳配额现货和期货都有。它的数据格式跟ICE不太一样,字段命名也任性。

交易所 主要产品 数据获取方式 费用
ICE EUA期货、CER期货 ICE Data Services API 付费(按数据量)
EEX EUA现货、期货、碳配额拍卖 EEX Market Data API 部分免费+高级付费
我的经验:如果只是做研究,EEX的免费数据够用了。但实盘交易,ICE的数据更全、延迟更低。别问我怎么知道的——踩过坑。

API数据接口调用实战

嗯,这里要注意。API调用不是简单的发个请求就完事。我见过太多人卡在认证这一步。

拿ICE的API举例,它用的是OAuth2.0认证。你得先注册应用,拿到client_id和client_secret。然后每次请求前先换token。

import requests
import pandas as pd
from datetime import datetime, timedelta

# ICE API 认证示例
def get_ice_token(client_id, client_secret):
    url = "https://api.ice.com/oauth/token"
    payload = {
        "grant_type": "client_credentials",
        "client_id": client_id,
        "client_secret": client_secret
    }
    response = requests.post(url, data=payload)
    return response.json()["access_token"]

# 获取EUA期货历史数据
def fetch_eua_futures(token, contract_code, start_date, end_date):
    url = f"https://api.ice.com/marketdata/v2/history"
    headers = {"Authorization": f"Bearer {token}"}
    params = {
        "symbol": contract_code,
        "start": start_date,
        "end": end_date,
        "interval": "daily"
    }
    response = requests.get(url, headers=headers, params=params)
    data = response.json()
    
    # 解析成DataFrame
    df = pd.DataFrame(data["data"])
    df["date"] = pd.to_datetime(df["date"])
    return df

# 调用示例
token = get_ice_token("your_client_id", "your_client_secret")
eua_data = fetch_eua_futures(token, "EUA_DEC2024", "2024-01-01", "2024-06-30")
print(eua_data.head())
我曾经踩过的坑:API有频率限制!ICE的免费层每分钟最多30次请求。我一开始没注意,写了个循环批量拉数据,结果IP被封了24小时。后来学乖了,每次请求之间加个sleep(2)。

数据清洗与预处理实战

数据拿到手,别急着用。原始数据脏得很。我总结了一套「三步清洗法」:去重、补缺、标准化。

第一步:去重与异常值处理

交易所的数据偶尔会重复推送。比如网络波动时,同一根K线可能收到两次。

# 去重
df = df.drop_duplicates(subset=["date", "contract"])

# 异常值检测:价格不能为负,成交量不能为0(非交易日除外)
df = df[(df["close"] > 0) & (df["volume"] >= 0)]

# 我习惯用Z-score检测极端值
from scipy import stats
z_scores = stats.zscore(df["close"])
df = df[(abs(z_scores) < 3)]  # 剔除3个标准差以外的数据

第二步:缺失值填充

碳期货市场有节假日,非交易日自然没数据。但有些时候,明明是交易日,数据却丢了——可能是交易所系统延迟。

我的做法是:先检查是不是真的缺失,还是数据还没推送。如果是真缺失,用前向填充。

# 检查缺失
print(df.isnull().sum())

# 前向填充(用上一个交易日的数据)
df["close"] = df["close"].ffill()
df["volume"] = df["volume"].fillna(0)  # 成交量缺失视为0

# 如果连续缺失超过3天,我建议直接删除
df = df[df["close"].notna()]  # 删除收盘价缺失的行
关键点:碳期货有主力合约切换的问题。不同月份的合约价格不能直接拼接。你需要做「连续合约」处理——要么用持仓量最大的合约,要么用展期收益率调整。

第三步:数据标准化

不同交易所的数据格式不一样。ICE用美式日期(MM/DD/YYYY),EEX用欧式(DD.MM.YYYY)。字段名也不同——ICE叫"settle_price",EEX叫"settlement_price"。

我统一转成ISO格式,字段名也统一。

# 统一日期格式
df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d")

# 统一字段名
df = df.rename(columns={
    "settle_price": "close",
    "settlement_price": "close",
    "volume_traded": "volume"
})

# 添加辅助字段
df["year"] = df["date"].dt.year
df["month"] = df["date"].dt.month
df["day_of_week"] = df["date"].dt.dayofweek  # 0=周一

数据清洗流程图

下面这张图是我自己画的数据处理流程,每次做新项目都拿出来参考。

碳期货数据清洗流程图 数据获取 ICE / EEX API 去重处理 drop_duplicates() 异常值检测 Z-score / 业务规则 缺失值填充 ffill() / 删除 数据标准化 统一格式/字段名 清洗完成 存储为Parquet/CSV 注意:每一步都需要记录日志,方便回溯问题 分支处理:主力合约切换时,需要单独处理展期逻辑 建议将原始数据、清洗后数据分开存储

实战中的几个坑

做数据清洗这么多年,我总结了几条血泪教训:

  • 时区问题:ICE的数据用UTC时间,EEX用CET。我一开始没注意,导致回测结果差了整整一天。后来统一转成UTC+0。
  • 合约代码变化:ICE偶尔会改合约代码。比如EUA期货以前叫"EUA",后来改成"EUA Futures"。你的代码得兼容历史数据。
  • 数据延迟:EEX的免费数据有15分钟延迟。做日内策略的话,这个延迟会要命。我建议付费买实时数据。
我的小技巧:每次清洗完数据,都生成一份数据质量报告。包括缺失率、异常值数量、时间跨度等。这样下次用数据时,心里有底。

好了,数据获取和清洗这块就聊到这儿。记住一句话:数据质量决定策略上限。花80%的时间在数据上,剩下20%做策略,这是值得的。

专注资料整理