3、核心数据结构设计:定义价差合约对象、设计K线数据结构、设计价差数据结构、数据存储方案(CSV/HDF5)
好,咱们进入正题。数据结构这东西,说白了就是程序的骨架。骨架搭歪了,后面写再多代码也是白搭。我在做第一个价差分析工具时,就吃过这个亏——一开始图省事,用了一堆散乱的字典和列表,结果后来加功能时改得想哭。所以这一章,咱们把地基打牢。
3.1 定义价差合约对象
先聊聊价差合约。你想想看,一个价差合约本质上是什么?就是两个(或多个)期货合约的配对。比如做豆油和棕榈油的价差,你得知道它们各自的合约代码、交易所、乘数、最小变动价位等等。
我个人习惯用一个类来封装这些信息。这样后面用起来顺手,也方便扩展。
class SpreadContract:
"""价差合约对象"""
def __init__(self, leg1_code, leg2_code, leg1_multiplier=1.0, leg2_multiplier=1.0):
self.leg1_code = leg1_code # 腿1合约代码,如 'Y2401'
self.leg2_code = leg2_code # 腿2合约代码,如 'P2401'
self.leg1_multiplier = leg1_multiplier # 腿1乘数
self.leg2_multiplier = leg2_multiplier # 腿2乘数
self.exchange = None # 交易所
self.tick_size = None # 最小变动价位
self.name = f"{leg1_code}-{leg2_code}" # 价差名称
def __repr__(self):
return f"SpreadContract({self.name})"
这里有个细节:乘数。为什么要有乘数?因为不同合约的合约单位可能不一样。比如你做螺纹钢和热卷的价差,它们都是10吨/手,乘数就是1:1。但如果你做股指期货和ETF的价差,乘数就得仔细算了。我在项目中遇到过这种情况,当时没设乘数字段,后来硬着头皮改代码,麻烦得很。
3.2 设计K线数据结构
K线数据是价差计算的基础。说白了,每条K线就是一根蜡烛,包含开盘、最高、最低、收盘、成交量这些信息。但咱们做价差分析,还需要额外记录一些东西。
我推荐用 namedtuple 或者 dataclass 来定义。Python 3.7+ 的话,直接用 dataclass 最省事。
from dataclasses import dataclass
from datetime import datetime
@dataclass
class Bar:
"""单条K线数据"""
datetime: datetime # 时间戳
open: float # 开盘价
high: float # 最高价
low: float # 最低价
close: float # 收盘价
volume: float # 成交量
open_interest: float # 持仓量(可选)
symbol: str = '' # 合约代码
嗯,这里要注意:持仓量对于价差分析其实挺重要的。为什么?因为价差策略往往需要判断市场情绪,持仓量的变化能反映资金进出。我刚开始做的时候忽略了这字段,后来发现有些策略信号需要持仓量配合验证,又回头补上。
3.3 设计价差数据结构
价差数据怎么存?这得看你怎么算价差。最简单的就是直接相减:价差 = 腿1价格 - 腿2价格。但实际中还有比例价差、对数价差等等。
我个人习惯把价差数据设计成两个部分:
- 原始价差序列:包含时间戳和计算后的价差值
- 统计特征:均值、标准差、分位数等,方便后续分析
@dataclass
class SpreadData:
"""价差数据结构"""
datetime: datetime # 时间戳
spread_value: float # 价差值
leg1_price: float # 腿1价格
leg2_price: float # 腿2价格
z_score: float = 0.0 # Z-score(可选)
signal: int = 0 # 交易信号(可选,1做多,-1做空,0观望)
def __post_init__(self):
"""初始化后自动计算一些衍生字段"""
self.spread_change = 0.0 # 价差变动量,后续可填充
你看,我加了个 z_score 字段。这玩意儿在统计套利里很常用,表示当前价差偏离均值多少个标准差。说白了,就是帮你判断当前价差是不是「异常」了。
3.4 数据存储方案:CSV vs HDF5
数据存哪?怎么存?这是每个做量化的人都要面对的问题。我两种都用过,说说我的感受。
3.4.1 CSV方案
CSV 的好处是简单、通用。任何工具都能打开,Excel 也能直接看。适合小规模数据、快速原型开发。
import pandas as pd
# 保存为CSV
df.to_csv('spread_data.csv', index=False)
# 读取CSV
df = pd.read_csv('spread_data.csv', parse_dates=['datetime'])
但 CSV 有个硬伤:读写慢,而且不支持数据类型。你存个浮点数进去,读出来可能变成字符串,还得自己转。我曾经有个项目,每天生成几百万条 tick 级价差数据,用 CSV 存,结果回测时加载数据花了半小时……
3.4.2 HDF5方案
HDF5 是二进制格式,读写快,支持压缩,还能存储复杂的层次结构。对于价差分析这种需要频繁读写历史数据的场景,HDF5 是更好的选择。
# 保存为HDF5
df.to_hdf('spread_data.h5', key='spread', mode='w', complevel=5)
# 读取HDF5
df = pd.read_hdf('spread_data.h5', key='spread')
# 增量追加
df_new.to_hdf('spread_data.h5', key='spread', mode='a', append=True)
你看,HDF5 支持 append 模式,每天的新数据可以直接追加进去,不用重写整个文件。这个特性在实盘场景下特别有用。
| 特性 | CSV | HDF5 |
|---|---|---|
| 读写速度 | 慢(文本解析) | 快(二进制) |
| 文件大小 | 较大 | 较小(支持压缩) |
| 跨平台性 | 极好 | 好 |
| 数据类型支持 | 弱(全是字符串) | 强(原生类型) |
| 适合场景 | 小数据量、调试 | 大数据量、生产环境 |
3.5 整体数据流架构
说了这么多,咱们把整个数据流串起来看看。下面这张图展示了从原始行情到价差数据的完整链路。
从这张图你能看到,数据从多个源头进来,经过解析、计算,最后落到存储层。每个环节的数据结构我们都定义好了,后面写代码就是填空的事。
好了,这一章的内容就到这。数据结构这块虽然看起来枯燥,但确实是整个工具的基石。你花半小时把数据结构设计好,后面能省下好几个通宵的调试时间。我自己就是这么过来的,深有体会。