📊 量化回测框架 · 数据结构

OHLCV · 多标的 · 避坑 中小学生色系 ✨

📐 题目4: 数据结构设计 —— OHLCV、DataFrame操作、时间序列索引、多标的DataFrame。掌握这些,回测框架就稳了一半。

📈 OHLCV 数据结构 Open, High, Low, Close, Volume
字段含义类型避坑注意
Open开盘价float64注意复权调整,前复权/后复权一致性
High最高价float64不可低于Close或Open (数据源异常)
Low最低价float64不可高于Close或Open
Close收盘价float64用于计算收益率,注意缺失值填充
Volume成交量int64 / float单位统一(股/手),除权后调整
  • 💡 避坑1: 检查 OHLC 逻辑关系 (High >= max(Open,Close) , Low <= min(Open,Close))
  • 💡 避坑2: 时间索引必须严格升序,且无重复时间戳 (duplicated)
  • 💡 避坑3: 使用 pandas 的 Float64 可保留空值,避免精度陷阱
🕒 DataFrame 操作 & 时间序列索引 pandas 核心
  • 索引设置: df.set_index('datetime', inplace=True) 或 pd.DatetimeIndex
  • 频率对齐: 使用 resample('1D').agg(...) 或 asfreq 避免非交易时段干扰
  • 滑窗/滚动: rolling(window=20).mean() 注意 min_periods
  • 缺失处理: 前向填充 ffill() 或插值,但不可引入未来信息
  • ⚠️ 避坑: 时间索引时区统一,避免 tz_localize 与 tz_convert 混淆

🧪 常用操作速查

df['return'] = df['Close'].pct_change()
df['ma20'] = df['Close'].rolling(20).mean()
df_week = df.resample('W').agg({
    'Open': 'first', 'High': 'max',
    'Low': 'min', 'Close': 'last',
    'Volume': 'sum'
})
⏳ 时间序列索引 (升序, 无缺口) 2023-01-03 2023-01-04 2023-01-05 2023-01-06 2023-01-07 📅 交易日历
🧩 多标的 DataFrame 设计 MultiIndex / 分层列
方法结构适用场景
MultiIndex 行(datetime, asset)长表,适合 groupby 操作
MultiIndex 列asset1, asset2… 每列OHLCV宽表,方便截面分析
字典/panel{asset: DataFrame}旧版pandas,不推荐
  • 推荐: MultiIndex 行 (时间+标的) 配合 groupby('asset')
  • 避坑: 不同标的交易日期可能不同,使用 reindex 统一索引
  • 避坑: 列名冲突时使用 pd.MultiIndex.from_product
📦 多标的DataFrame 架构 (MultiIndex 行) datetime asset Open High Low Close Vol 2023-01-03 AAPL 130.2 132.1 129.8 131.5 2.1M 2023-01-03 GOOGL 98.5 99.2 97.9 98.8 1.5M ✅ 使用 .loc[(slice(None), 'AAPL'), :] 切片
🧪 实战代码 · 多标的DataFrame MultiIndex 创建与操作
import pandas as pd
import numpy as np

# 模拟多标的OHLCV数据 (长表MultiIndex)
dates = pd.date_range('2023-01-01', periods=5, freq='D')
assets = ['AAPL', 'GOOGL', 'MSFT']
index = pd.MultiIndex.from_product([dates, assets], names=['datetime', 'asset'])

data = np.random.uniform(100, 200, (len(index), 5))
df = pd.DataFrame(data, index=index, columns=['Open','High','Low','Close','Volume'])
df['Volume'] = (df['Volume'] * 1000).astype(int)

# 操作:获取 AAPL 的 Close 序列
aapl_close = df.loc[(slice(None), 'AAPL'), 'Close']
print(aapl_close)

# 避坑:统一索引 (不同标的交易日可能不同)
df_wide = df.unstack('asset')  # 转为宽表
df_wide = df_wide.reindex(pd.date_range('2023-01-01','2023-01-07', freq='D'))  # 对齐
            

⚠️ 避坑重点: 多标的DataFrame 务必注意 sort_index(),否则切片可能失效;使用 stack/unstack 时注意层级顺序。