做商品期货研究的人,十有八九都会卡在同一个问题上:日频的商品指数行情,到底上哪儿找一份干净、连续、口径统一的数据?自己去交易所抓原始合约不是不行,但主力合约换来换去,近月远月价差又大,拼出来的序列根本没法直接做回归。我之前做一个跨资产定价的课题就卡在这一步整整两天,后来换了思路,用南华期货指数(以下简称该系列指数)的日线行情数据,问题才算理顺。正好近期不少人在问某开放数据平台(CnOpenData,以下简称该数据平台)上这份数据的结构和用法,这篇就把数据怎么理解、怎么上手、怎么避坑,完整拆一遍。
这篇内容适合三类人看:准备写金融实证论文的研究生,商品量化策略刚入门的开发同学,以及需要把商品行情纳入宏观资产框架的分析师。读完你至少能少走两周弯路。
1. 这份数据到底解决什么问题
1.1 期货指数为什么比原始合约好用
先想清楚一个基础问题:单看某个期货合约的日线,为什么没法直接用来做长期研究?
核心原因是期货合约有到期日。螺纹钢主力合约每个月都会换,你可能用2310合约跑完三个月,下个月主力变成2311,两个合约的价格水平、成交活跃度都不一样,直接拼接会产生大量的人为跳空。更麻烦的是,不同品种一手是多少吨、保证金比例多少、最小变动价位多少,全都不同,要把铜、原油、豆粕的价格放在一个体系里比较,几乎没有可比性。
商品期货指数的本质,是把一个板块或整个市场的价格信息,通过一套固定的规则压缩成一条连续的指数序列。你可以把它理解为商品市场里的“宽基指数”。股票研究里有万得全A、沪深300,商品研究里就需要类似的编制产物。这类指数天然解决了品种连续性问题,也屏蔽掉了单合约换月的噪声。
该系列指数就是这样一个存在:它把期货市场的主要品种按其重要性和流动性加权,合成综合指数,再按板块拆出分类指数。日线数据一旦连续,收益率计算、波动率估计、动量因子构造全都能直接在序列上做,这才是数据价值最大的地方。
1.2 数据平台的整理工作帮你省了什么
自己从头整理指数日线,至少要走五步:找原始行情、核对各品种每个交易日的结算价、处理合约换月、按权重规则合成指数、再对齐交易日历。这里每一步都有坑,尤其是换月规则的细节,各家机构可能都不一样,结论稍微受点影响就会被人质疑。
该数据平台把这层工作直接做好了,以结构化的表格形式放出日线行情。数据拿到手,字段已经帮你分好,不需要再去翻不同交易所的原始公告,也不需要对指数编制规则做二次猜测。你只需要关心后续自己的清洗和研究逻辑就行,前端的脏活累活省掉一大半。
另外我建议大家把这套数据和原始行情数据配合使用。做样本外验证、做稳健性检验时,用机构编制的指数跑一遍,再自己用原始合约粗合成一个对照组,两套结果交叉验证,说服力会强得多。
2. 数据结构与字段背后的口径细节
2.1 数据集常见的字段构成
这份数据最核心的载体就是一张日频行情表,每一行代表某个指数在某个交易日的一段行情概要。我在实际使用中,通常会先确认这张表包含的字段。这类数据集的字段设计一般大同小异,常见格局如下。
| 字段 | 类型 | 含义 | 使用注意 |
|---|---|---|---|
| date | datetime | 交易日期 | 夜盘交易归属次日,务必先确认 |
| code | str | 指数代码 | 不同板块、不同版本的区分标识 |
| name | str | 指数名称 | 综合、工业品、农产品、能化、金属等 |
| open | float | 开盘价 | 该交易日第一笔成交价 |
| high | float | 最高价 | 含夜盘时段的最高值 |
| low | float | 最低价 | 含夜盘时段的最低值 |
| close | float | 收盘价 | 日盘收盘时的最后成交价 |
| settlement | float | 结算价 | 当日结算用价格,做研究时最常用 |
| volume | int | 成交量 | 注意确认单边还是双边口径 |
| open_interest | int | 持仓量 | 做持仓分析时核心字段 |
字段不多,但每列的口径都值得较真。尤其是 settlement 和 close 的关系,很多人因为搞混它们,后期收益率算出来和官方数据对不上,查半天不知道问题出在哪。
2.2 收盘价与结算价的区别
国内商品期货每天收盘后都会有一个交易所统一的结算价,这个价格不是简单把最后一笔成交价拿来用,而是根据全天成交的加权平均算出来的,和股票里的收盘价逻辑完全不同。
做保证金、盯市、结算,用的是结算价。但很多行情软件默认展示的“收盘价”是最后一笔成交价,两者正常情况下差距不大,遇到极端行情或者尾盘异动时可能差出不少。研究日收益率时,到底该用哪个?我的习惯是:做策略回测用结算价,因为它更贴近真实可成交的盈亏水平;做技术形态分析用收盘价,因为它反映市场最终的交易情绪。如果你跑出来的累计收益曲线和官方发布的指数收益对不上,八成就是把这两个弄混了。
该数据集如果同时提供了 close 和 settlement 两个字段,那最好都留着一会儿用。只保留一个的做法,会限制后续很多分析角度。
2.3 夜盘归属最容易踩坑
国内商品期货是有夜盘时段的,晚上21点到次日凌晨。问题在于:这些夜盘成交,在日线数据里到底记在当天还是第二天?
按国内交易所的普遍规则,夜盘时段归入下一交易日。比如周一夜盘产生的成交,会计入周二的日线里。这就意味着,周二的日线数据里,包含了周一晚上那一段价格波动。这个规则如果你没注意到,做事件研究时会莫名奇妙地滞后一天,尤其在分析隔夜信息冲击时,结论会完全错位。
判断方式也不难:把某个品种的日线数据和分时数据拉开对比,看看放量时段的时间归属就知道了。像这种细节,普通文档里不会写,但实际研究里一旦踩到,排查成本极高。
3. 指数编制逻辑与数据层面的陷阱
3.1 指数编制逻辑大概怎么理解
理解底层的指数编制,不是为了去复现它,而是为了知道什么场景能用、什么场景不能用。这份数据背后遵循的是一套常见的商品指数加权逻辑,核心环节可以概括为两步:
第一步是选择成分品种。综合指数会把主要的工业品、农产品、金属、能源化工品种纳入进来,剔除掉流动性太差、上市时间太短的合约。第二步是确定权重。通常以持仓量和成交量为依据计算每个品种的权重,再定期再平衡。这样权重能实时反映市场交易结构的转移,比如某个品种成交突然大涨,它对指数的边际影响也会变大。
分类指数则是在这个框架下做子集,比如能化板块指数只纳入化工和能源类品种,对研究板块轮动和跨板块对冲很有用。理解这套逻辑后,你看到指数点位变化时,就不会把“指数涨了”直接等同于“所有品种都涨了”,而是会去思考哪些高权重品种在拉动,这是做归因分析的基本功。
3.2 用日线数据做研究时的三个典型陷阱
第一,跨品种拼接价格。有些同学为了扩大样本,把不同分类指数连在一起分析。这种做法不是不行,但需要明确分类指数之间的成分重叠度和权重差异。硬拼三条指数序列跑回归,大概率是在比较不同篮子,而不是比较同一市场状态。
第二,时间窗口不统一。不同分类指数的数据起点可能不同,金属板块可能比能化板块早上市多年。合并面板数据时,如果不先对齐时间起点,样本区间会被悄悄缩短,或者出现大量缺失。拿到数据后第一件事,就是看一眼每类指数各自最早的数据日期。
第三,指数点位与直接加总不匹配。很多人会拿指数成分品种的价格按自己理解的权重去还原指数点位,结果发现对不上。这里的原因通常是权重调整频率不一致,或者编制方对某些品种做了标准化处理。遇到这种情况不要纠结,直接用官方指数序列就好。
4. 拿到数据之后的完整处理流程
4.1 数据读取与初步体检
不管数据来源多正式,拿到手先做体检永远是第一步。我会先读取文件,大致看一眼数据范围、字段类型、缺失情况,然后再动手处理。以 Python 为例,标准的操作流程大概是这样的:
import pandas as pd df = pd.read_csv("nanhua_futures_index_daily.csv", parse_dates=["date"]) print(df.shape) print(df.dtypes) print(df.head(10)) print(df.tail(10)) # 检查每个指数的时间范围和缺失情况 for code, group in df.groupby("code"): print(code, group["date"].min(), group["date"].max(), group.isna().sum().sum())这一步能快速暴露很多问题。比如某些指数的交易天数明显偏少,某些字段大面积缺失,甚至会出现日期断层。这些问题越早发现越好,不要等到建模做到一半才发现时间序列里缺了一段。
体检完之后,把 date 列设为索引,按 code 分组排序,保证每个指数内部的时间顺序正确。这里我有一个小习惯:不会直接覆盖原始数据,而是另存一份 processed 版本,处理完一步就保存一次中间结果。数据处理流程走到一半发现前面某步错了,直接重跑就行,不用从头再来。
4.2 交易日历对齐与缺失值处理
商品期货指数不是每天都更新的。遇到法定节假日、交易所休市日,序列里就没有那一行。但很多宏观数据或者股票数据在这些日期可能有值,合并的时候如果不注意,就会产生错位或者虚假缺失。
最稳妥的办法是准备一份交易日历,把指数数据和这份日历做外连接,确保日期序列是连续的。比如在分析商品价格和宏观政策变量的关系时,宏观变量可能每月发布一次,商品指数是每天一条,就需要先把高频数据降频到月度,再和宏观数据对齐。降频的方法也要考虑清楚,是用月末取值还是当月均值,这会直接影响研究结论。
缺失值处理要克制。“前向填充”这种技术看起来很优雅,但用在价格数据上容易制造假象。如果某一天指数确实没有交易,填充出来的价格并非真实成交价,基于它计算收益就会失真。我更推荐的做法是:把缺失日期直接保留为空,计算收益率时自动跳过,而不是人为补一个数字。除非你的后续模型明确要求等距时间序列,否则不要急着填充。
4.3 构建研究常用指标
数据清洗干净后,就可以开始构建指标了。日线行情最基础的衍生指标是日收益率和滚动波动率,这两个指标几乎所有的时序分析都会用到。日收益率一般用自然对数差分,数学性质好,方便跨周期累加:
df["ret"] = df.groupby("code")["settlement"].transform(lambda x: np.log(x / x.shift(1))) df["vol_20d"] = df.groupby("code")["ret"].transform(lambda x: x.rolling(20).std() * np.sqrt(252))做动量因子的话,通常会看过去12个月的累计收益,但剔除最近1个月,这在商品期货研究里是一个比较常用的惯性式构造方式。构造的时候记得用 shift 避开未来数据,防止前视偏差。
我之前做过一个跨商品类别的动量策略回测,用该系列指数的日线数据,动量因子构造完执行效果不错。但真正容易出错的其实是因子对齐:因子值要用截至 t 日的数据算,收益要用 t+1 日开始算,很多人写着写着就把当期因子和当期收益放在一起回归,最后归回一个虚高但完全不可外推的结果。
4.4 一个最小复现示例
把上面这些串起来,做一个小而完整的验证:检验一下每类指数是否存在时间序列动量。简单来说,就是看过去的涨跌能不能预测未来的涨跌。代码如下,非常简练:
import numpy as np from scipy import stats results = [] for code, group in df.groupby("code"): g = group.dropna(subset=["ret"]).copy() g["mom_12_1"] = g["ret"].rolling(252).sum() - g["ret"].rolling(21).sum() g["ret_future"] = g["ret"].shift(-1) valid = g.dropna(subset=["mom_12_1", "ret_future"]) if len(valid) > 200: slope, intercept, rvalue, pvalue, se = stats.linregress(valid["mom_12_1"], valid["ret_future"]) results.append({"code": code, "斜率": slope, "t值": slope / se, "r方": rvalue**2, "样本量": len(valid)}) result_df = pd.DataFrame(results) print(result_df.sort_values("t值"))这个分析虽然基础,但很有参考价值:它能快速告诉你该系列不同分类指数的动量特征是否有明显差异。如果某些板块r方明显较高,说明这个板块的策略容量和配置价值可能和其他板块完全不同,值得进一步深挖。
代码里最容易被忽略的是shift(-1)这步,它确保未来收益不会和当前动量因子重叠。这种细节在教科书里经常一笔带过,但在真实数据处理中,少了这一步整个分析就废了。
5. 常见问题排查速查表
在实践中遇到的问题,比原理更值得总结。我把这些年用各类日线数据最常踩的几个坑整理成表格,碰到类似情况可以直接对照处理。
| 问题现象 | 可能原因 | 处理办法 |
|---|---|---|
| 某一天数据缺失 | 法定节假日、临时休市、数据源更新延迟 | 对照交易所日历确认是否正常休市,若正常则保留空值 |
| 点位和行情软件对不上 | 用了收盘价而非结算价,或复权口径不同 | 优先用结算价字段,重点核对数据版本说明 |
| 收益率出现极大异常值 | 节日跳空、极端行情、初始权重再平衡 | 分日期区间检查,确认后再决定是否缩尾 |
| 成交量/持仓量数值巨大 | 单双边口径不同 | 核对数据说明文档,单双边做研究时统一口径 |
| 不同指数时间起点不一致 | 品种上市时间不同 | 合并面板时统一对齐到公共样本区间 |
| 读取速度很慢 | CSV 文件过大、日期未解析为索引 | 用 parse_dates 读日期,再存成 Parquet 格式做二次读取 |
| 和宏观数据合并后样本骤减 | 高频低频降频方式不一致 | 统一用月末取值或窗口均值,保证口径一致 |
这七个问题占了我日常数据排查工作量的八成以上。问题本身不难,难在刚从别人手里拿到数据时,很难判断是自己的操作问题还是数据本身的问题。遇到对不上的情况,先别急着怀疑数据有误,优先回看口径说明,基本面九成的“数据错误”最后都是口径错误。
还有一个排查技巧值得单独说:把数据可视化出来。直接画出每个指数的收盘价时间序列,扫一眼就能发现肉眼可见的异常,比如突然归零、剧烈跳变、长时间横盘。先用眼睛看一遍再进入定量分析,比写一堆统计检验函数更快。
6. 一些实在的使用建议
数据格式方面,如果可以的话,我强烈建议第一时间把 CSV 转成 Parquet 格式。CSV 在读取大文件和重复筛选时确实太慢,尤其当你同时分析多个分类指数、反复滚动计算指标时,存储格式的差异会被放大很多倍。转存 Parquet 后,文件的读取速度能快一个数量级,而且还能保留字段的数据类型信息,不用每次读取都重新指定解析规则。
样本区间选择上,我建议尽量使用指数发布后稳定运行一段时间的数据。刚上市试运行阶段的指数,其成分选择和权重机制可能还不够稳定,样本外表现也会受影响。如果你做的是稳健性检验,可以分别用早期数据和近期数据各跑一遍,看看结论是否一致。
关于用指数做研究还有一个容易忽略的点:机构的指数设计有自己的目标导向,有些偏重可投资性,有些偏重市场表征。该系列指数更多是反映现货市场的综合价格趋势,而不是某个可以真正下单交易的组合。做资产配置回测时,要充分考虑这个差异,否则会把指数收益直接当成策略可实现的收益,高估策略效果。
最后再分享一个我在实操中积累的个人体会。
这几年做下来,我最大的感触是:数据整理的时间往往比建模还长。刚开始我也着急,想赶紧跳到模型环节,但凡是多花点时间在数据结构、口径确认上,后面返工的概率就会大幅下降。用这份南华期货指数日线数据时,如果你能先把文中提到的字段口径、夜盘归属、缺失值处理三个点梳理一遍,你的研究已经赢过了大半的人。
另外,我有个小建议:不要只依赖单一机构的指数。作为一个稳健性测试,可以引入其他商品指数做交叉验证。不同编制逻辑下,同一段行情的结论可能略有差别,但大方向一致的话,你的研究结论会更可信。数据只是研究的地基,把地基压实,上面的模型才站得住。