ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从多因子模型到决策树:Python与SPSS构建金融期货仓位优化策略

从多因子模型到决策树:Python与SPSS构建金融期货仓位优化策略 做量化研究的人多少都经历过这样一段纠结期你手里同时握着好几组数据单看每个都有道理——沪深300是市场的锚申万风格指数告诉你资金在偏爱大盘还是小盘10年期国债收益率牵动着所有资产的定价分母期权波动率指数又像一台情绪探测器。可真要拿它们一起建策略的时候又总觉得互相打架、使不上劲。我这次做的事情就是把这些数据和模型完整地串成一条研究链路用 Python 和 SPSS 配合先跑通单指数模型和 FF 三因子模型再用决策树做市场状态识别最终把信号落到金融期货的仓位和风险参数优化上。这篇文章不打算写颠簸的教程集合而是把从数据清洗到策略回测的完整过程复盘一遍该踩的坑、该避的偏差都会摆在台面上说。1. 为什么要把这四类数据放在一个项目里背景与总体框架1.1 项目的起点指标都讲得通组合起来却打架先说清楚我为什么会启动这个项目。有一段时间我在回测股指期货策略时发现用沪深300指数的动量或者均线系统在2017年和2020年下半年效果尚可但一旦遇到2015年那样的极端波动、2018年的单边阴跌纯指数层面的信号几乎全部失效。问题不在于指数本身没有信息量而是单纯拿指数做预测等于默认所有股票同涨同跌、市场风格永远不变。可事实上A股的结构性分化非常剧烈——小盘股和大盘股之间、高估值和低估值板块之间经常走出完全相反的行情。后来我尝试把申万风格指数加进来。申万的大盘/小盘、高市盈率/低市盈率指数直接刻画了市场在两个维度上的偏好市值维度和估值维度。这两个维度正好对应 Fama-French 三因子模型里的 SMB 和 HML 因子。也就是说申万风格指数不仅是描述工具还天然适合用来构造最经典的多因子模型的解释变量。市场基准、风格结构都齐了剩下两个变量则是从不同侧面补充信息10年期国债收益率决定无风险利率影响所有资产的估值中枢300ETF期权波动率指数则是市场情绪和尾部风险的温度计。把这些数据放到一起等于把一个黑箱市场拆解成几个可解释的侧面涨什么沪深300、谁在涨风格因子、钱的机会成本国债收益率、市场怕不怕波动率指数)。这个框架也决定了后面的模型顺序——先用单指数模型证明单靠市场不够再用三因子模型补上风格维度最后用决策树把多维特征整合成可操作的期货策略信号。1.2 每类数据在项目里的角色分工我把四类数据分成了三层角色后面所有代码和模型都是围绕这三层展开的基准层沪深300指数作为市场组合的代理变量。单指数模型里它是唯一的解释变量三因子模型里它是市场因子决策树的特征里也有它的收益和波动。风格层申万风格指数核心用途是构造 SMB 和 HML。我做了一个很实用的替代方案——直接用申万小盘指数减申万大盘指数近似 SMB用申万低市盈率指数减申万高市盈率指数近似 HML。严格说这不是学术上最标准的 Fama-French 因子构造法但胜在数据易得、逻辑透明而且和官方因子的相关性相当高足够支撑策略层面的研究。宏观与情绪层10年期国债收益率主要做两件事一是换算成日频无风险利率放进所有回归模型做超额收益处理二是作为独立的宏观特征比如20日变化量、期限利差用于决策树识别利率环境。300ETF期权波动率指数则作为情绪特征我格外关注它的历史分位数和短期变化率而不是绝对数值。目标层金融期货主要是沪深300股指期货IF和10年期国债期货T。股票类因子模型给出的信号用来调整IF的净敞口宏观利率信号用来调整T的久期暴露。这套分工在项目一开始就定死了好处是后面每一步都有明确的经济学含义不会变成纯粹的数据挖掘游戏。2. 数据准备跨源数据对齐比建模型更早遇到坎2.1 数据来源与口径说明这个项目的真实工作量大概有40%是花在数据准备上。先交代我用的数据口径方便读者对照复现沪深300指数用收盘点位计算日频对数收益率复权不需要处理。申万风格指数选取申万大盘、申万小盘、申万低市盈率、申万高市盈率四个指数同样用收盘点位算日频收益率。10年期国债收益率用中债10年期国债到期收益率日度序列单位是百分比。这个序列有几个特性要注意一是节假日的缺失比股票多因为债券市场只在工作日交易且收得早二是它本身就是收益率而不是价格所以要先除以100再除以252转成日频无风险利率。300ETF期权波动率指数我自己基于300ETF期权合约按类似VIX的方法做了估算核心思路是把近月与次近月合约中虚值看涨看跌期权的隐含波动率按行权价加权拼接。如果你想省事也可以直接使用市场上公开的波动率指数序列但务必确认它的编制方法和涨跌方向是波动率点位还是百分比变化否则后面分位数计算会出偏差。数据频率统一为日频。最麻烦的是交易日历对齐股票和期货的交易日基本一致但国债收益率的交易日更少直接用 inner join 会损失样本。我采用的办法是先用 ffill 补齐债券序列的缺失日再对所有序列取内积对齐只在指数收益率缺失时才丢弃当日。这样既保住了交易日数量也不会凭空引入不存在的收益。2.2 Python数据清洗与对齐实操数据清洗的代码很简单但细节特别容易翻车我直接贴核心段import pandas as pd import numpy as np # 假定已经按日期索引读入全部原始序列 df pd.DataFrame({ hs300: hs300_close, sw_large: sw_large_close, sw_small: sw_small_close, sw_highpe: sw_highpe_close, sw_lowpe: sw_lowpe_close, r10y: r10y_yield, # 单位% vol_idx: vol_index }) # 国债收益率用前值填充 df[r10y] df[r10y].ffill() # 指数类序列若有缺失直接丢弃当日 df df.dropna() # 统一到日频收益率 rets df.pct_change().dropna() # 日频无风险利率年化收益率转为日频 rf df[r10y] / 100.0 / 252.0 # 指数与风格基准超额收益 exm rets[hs300] - rf这里有一个我特别想强调的点期权波动率指数的可得时点问题。300ETF期权在15:00收盘但当天收盘后才能根据结算价完整计算波动率指数。如果策略在当天14:50就要下单那当天收盘的波动率指数根本拿不到只能用到前一天的。所以我在构造特征矩阵时把 vol_idx 整体向后平移一格确保任何特征在T日都是T日收盘后能够获得的避免前视偏差。这个细节在学术回测里经常被忽略但在实际交易里就是致命伤。SPSS的一个实用价值在于数据体检。我会把对齐后的数据导出成 CSV在 SPSS 里做两件事第一是描述统计里检查异常值特别是波动率指数突然跳空或者国债收益率出现极端值肉眼比脚本更容易发现异常第二是相关性矩阵快速确认各序列之间的相关系数符号是否符合经济学直觉。比如市场超额收益和国债收益率 20 日变化的相关性如果是正的就要警惕是不是收益率符号方向搞错了。2.3 对齐后的第一个发现数据整理完我做的第一件事不是建模而是画一个 2016—2023 年的四维叠加图沪深300收益率、SMB 方向用申万小盘减大盘的累计差、10年期国债收益率 20 日变化量、波动率指数。虽然这种图信息密度高到几乎没法直接指导交易但它能非常直观地暴露不同区间市场的性格切换。比如你能看到 2021 年初之后SMB 因子从持续为负转为剧烈震荡同时波动率指数中枢抬升这两件事放在一起至少说明单因子策略在那个阶段会频繁打脸。这个发现直接支撑了项目的模型选型决策市场状态不是一成不变的用固定参数的单指数模型去一招鲜行不通必须引入风格因子和情绪因子并且要用决策树这种能自动切分状态空间的模型去识别现在处于什么状态。数据准备的成果不只是能跑的干净数据更是你对整个研究区间市场脾气的一次全面摸底。3. 从单指数模型到FF三因子两代定价模型在实战中的互补3.1 单指数模型在沪深300与申万风格指数上的表现单指数模型的回归形式很简单R_i − R_f α β(R_m − R_f) ε我把每个申万风格指数分别作为被解释变量沪深300超额收益作为唯一解释变量滚动 120 天估计。用 Python 写滚动回归是这样import statsmodels.api as sm def rolling_beta(y, x, window120): x_mat sm.add_constant(x) betas [] for i in range(window, len(y)): xw x_mat.iloc[i-window:i] yw y.iloc[i-window:i] model sm.OLS(yw, xw).fit() betas.append(model.params.iloc[1]) return pd.Series(betas, indexy.index[window:])这个阶段得到的统计结果其实已经能回答一个重要问题单指数模型对各风格指数的解释力R²差异极大。对申万大盘R²通常能到 0.8 以上但对申万小盘尤其在某些年份R²会掉到 0.5 甚至更低。这背后的含义是如果只用沪深300去解释市场那么小盘行情的很大一部分波动属于模型外的残差而残差里沉淀的恰恰就是风格切换的机会和风险。而且单指数模型的残差在 SPSS 里做了 Durbin-Watson 检验后通常落在 1.6—1.7 附近离 2 有明显距离说明残差存在正自相关。再用标准化残差和标准化预测值做散点图能看到明显的喇叭口形态这就是异方差的典型特征。这些诊断结果合在一起说明单指数模型的信息是不充分的遗漏了风格维度于是顺理成章进入 FF 三因子模型。3.2 用申万风格指数构造SMB与HML因子FF 三因子模型的形式是R_i − R_f α β_m(R_m − R_f) β_s SMB β_h HML ε关键是因子怎么构造。学术上标准的做法是按市值和账面市值比把股票分层这需要全市场个股数据。对策略研究来说没有必要我直接用申万风格指数做了轻量近似# 申万风格指数近似构造因子 SMB rets[sw_small] - rets[sw_large] # 小盘 - 大盘 HML rets[sw_lowpe] - rets[sw_highpe] # 低市盈率 - 高市盈率在把这些因子放进回归之前需要先做一次平稳性检查。我的做法是在 SPSS 里对 SMB 和 HML 序列分别做 ADF 单位根检验结果是p值远小于0.05说明收益差序列是平稳的不需要额外做差分。这一点说重要也重要因为如果因子序列不平稳回归结果就可能是假的。但收益差天然就是平稳序列这符合金融数据的常规经验。3.3 Python回归与SPSS回归诊断的相互印证三因子回归在 Python 里跑一遍、在 SPSS 里跑一遍两边结果互相对照这是我个人比较推荐的流程。Python 这边做批量和滚动估计方便SPSS 那边则胜在回归诊断的一键化。具体在 SPSS 里的操作路径是Analyze → Regression → Linear因变量放某个风格指数的超额收益自变量放市场超额收益、SMB、HMLStatistics 里勾选 Durbin-Watson 和 Collinearity diagnosticsPlots 里把标准化预测值放X轴、标准化残差放Y轴。这套操作下来残差图、自相关和共线性问题全都直观呈现。从结果看加入 SMB 和 HML 之后模型的 R² 提升非常明显尤其是小盘指数和低市盈率指数R² 从 0.55 能提升到 0.82 以上DW 统计量也更接近 2。这说明风格因子确实捕捉到了单指数模型遗漏的系统性收益来源。不过三因子并不是万能药对小盘指数做滚动回归时α 在某些区间仍然显著不为零说明还有更细的结构没有被因子模型消化。但这不影响我的最终目的——我不需要因子模型做到完全定价我只需要它告诉我风格因子当前的位置和趋势为决策树准备特征。这里再分享一个 SPSS 和 Python 结果偶发不一致的坑两边对缺失值和异常值的默认处理不同比如 SPSS 默认用列表删除而 pandas 的 dropna 可能只剔除当前回归窗口内的缺失。好在项目早期就把数据清洗统一到了 Python 端导出给 SPSS 的已经是完整面板后来再没有出现过两边结果对不上的情况。遇到这类问题不要怀疑统计学算错了先检查数据口径。4. 决策树不做收益预测做状态识别从因子到信号4.1 为什么非要用决策树而不是更复杂的模型很多人一听机器学习 量化第一反应就是上深度学习或者 XGBoost。我做决策树是有明确理由的。这个项目里的目标是金融期货的仓位优化而不是在未来5天精确预测沪深300涨跌百分之几。在 A 股和期货市场精确预测点位几乎不可能但识别市场状态——现在是风险偏好强的进攻环境还是要防守的防御环境——相对更可行也更符合仓位管理的需要。决策树恰好擅长这件事。它本质上是在做规则划分什么条件下该加仓什么条件下该减仓结果是一棵可以被人类读懂的树。这对策略落地极其重要因为我可以把树的路径直接翻译成交易规则比如波动率分位高于70%且国债收益率20日上行超过8bp时IF净敞口降到0.3。换成 XGBoost精度可能更高但解释性差黑箱信号一旦连续出错你根本不知道是哪里出了问题。我用的还是带限定深度的决策树最大深度限制在4层叶子节点最少样本数设在50配合 TimeSeriesSplit 做时序交叉验证最大程度防止过拟合。4.2 特征、标签与样本构造特征不是直接把原始数据丢进去而是把前面模型的结果提练成因子。我最终用到的特征集合如下表特征名构造方式经济含义mkt_beta_60沪深300超额收益对市场组合的滚动60日β当前市场弹性smb_ret_20SMB因子20日累计收益近一个月小盘风格强弱hml_ret_20HML因子20日累计收益近一个月价值风格强弱yield_chg_2010年期国债收益率20日变化bp利率环境变动方向yield_slope10年期国债收益率−2年期近似期限利差与宏观预期vol_pctile期权波动率指数当前值在近250日的分位数市场恐慌的相对位置vol_chg_5波动率指数5日变化率情绪短期加速方向hs300_ret_5沪深300未来不参与取过去5日收益短期动量/反转标签我设计成三分类取未来5个交易日沪深300股指期货的收益2进攻收益 0.5%1中性−0.5% 到 0.5%0防御收益 −0.5%需要说明这个阈值不是固定的。我用的是历史收益分位数来校准保证三类样本大致均衡避免树总是朝着样本最多的那类倾斜。4.3 树的路径解读与关键阈值训练出来的树第一层通常都是 vol_pctile。这个结果一点不意外因为波动率指数本身就是一个浓缩了市场静信息的高维特征。在我这次的数据里根节点的划分阈值大致在 0.73——也就是说当期权波动率指数处于过去一年73%分位以上时市场大概率处在紧张区过多仓位会显著拉高回撤。第二层和第三层会引入 yield_chg_20 和 smb_ret_20。典型的一条防御路径是波动率分位偏高 → 国债收益率20日上行 → 无论风格因子强弱未来5日IF都更容易收跌。这条路径对应的是利率快速上行 情绪恶化的宏观环境这时候股票类风险资产很难有表现不仅IF要降仓位甚至可以考虑通过国债期货做多头对冲。特征重要性里排前三位的是 vol_pctile、yield_chg_20 和 smb_ret_20mkt_beta_60 反而排到了后面。这个排序很好地呼应了项目最初的判断光看市场β解决不了风格和情绪的问题多源数据的增量价值恰恰体现在被传统因子模型当成残差的那部分信息里。5. 300ETF期权波动率指数把市场情绪量化进策略5.1 波动率指数的数据特性与处理要点300ETF期权波动率指数本质上是期权隐含波动率的加权平均反映的是市场对未来30天左右沪深300波动幅度的预期。它最典型的特征是均值回归和尖峰厚尾绝大多数时间在某个区间内来回震荡但一旦突破区间往往对应重大风险事件。对这个序列我做了两个关键变换。第一个是历史分位数 vol_pctile用滚动250日的百分位排名代替绝对点位。这样做的原因是波动率的绝对水平在不同年份中枢差异很大直接用波动率25这种绝对阈值2017年的高波动放到2021年可能只是中等水平。分位数天然做了标准化更适合放进决策树里做跨年份的状态切分。第二个变换是短期变化率 vol_chg_5用来捕捉情绪加速。隐含波动率可以连续多日缓慢上升这种钝刀子割肉的行情和一天之内骤然飙升对后市的影响是完全不同的。5日变化率越大说明市场恐慌在快速聚集这种时候即使绝对分位数还没有到极高位置也应该提前降杠杆。我的计算逻辑大致是这样# vol_idx 已经是日频序列 vol_pctile vol_idx.rolling(250).rank(pctTrue) vol_chg_5 vol_idx.pct_change(5)注意要把这两个特征整体后移一格理由在前面说过——当日收盘才能算出当日的波动率指数策略信号最早只能从次日开盘生效。5.2 波动率指数与期货策略的真实关系有一个常见的误解是波动率越高市场一定越跌。我在数据里验证的结果是波动率处于高位分位时未来5日IF收益率的下跌条件概率确实更高但并非每次都是大跌更稳定的是在高波动状态下IF的多头夏普比率显著下降也就是收益没增加多少波动却大得多。因此波动率指数信号的第一用途不是择时做空而是调整杠杆和风险预算。我最终的成本规则里vol_pctile 低于0.2且 vol_chg_5 为负时IF净敞口可以放到1.0vol_pctile 高于0.7时净敞口压到0.2以下同时把国债期货的多头久期提高。这本质上是一种风险平价式的调节——股票端波动加大时把风险预算转移到债券端。5.3 加入波动率特征后样本外提升了多少我用两个模型做了一次对比实验一个决策树只放指数、风格和利率特征另一个加入 vol_pctile 和 vol_chg_5。同样是2016—2023年回测、同样的标签和三分类规则加入波动率特征后防御类样本的识别准确率从58%提升到71%攻击类样本从62%提升到66%。最直观的改善是最大回撤从没有波动率特征时的21%压到了16%左右。这个结果说明期权波动率指数作为一种领先情绪指标其信息含量和沪深300、申万风格指数、10年期国债收益率是互补的。如果没有这一组特征决策树等于在闭着眼睛做风控。6. 从模型信号到金融期货策略参数化、回测与踩坑6.1 信号到仓位的映射规则决策树输出的是三类状态标签要变成期货策略还必须定义状态→仓位的映射。这里我没有选择满仓/空仓这种极端切换而是设计了一套连续化的净敞口映射规则因为极端切换在实盘里会带来过高的换手成本和冲击成本。规则如下决策树状态vol_pctileIF净敞口T久期暴露持仓周期进攻 0.31.00下一交易日持有中性0.3—0.60.50.5下一交易日持有防御 0.60.0可到−0.21.5下一交易日持有这个规则的核心逻辑是进攻状态吃股票端的弹性防御状态把风险预算挪到债券端保持组合整体风险敞口可控。为了进一步降低换手我还加了一个滞后确认条件只有当树的输出状态连续两天一致时才切换仓位否则维持原仓位。这一个简单动作就大幅削减了交易次数对净值曲线的平滑贡献相当明显。6.2 回测框架与绩效对比回测在 Python 里用自研的向量化框架完成。初始资金设1000万IF保证金按12%计算T保证金按2.5%计算手续费按成交金额的万分之0.5双边滑点按每手1个tick。之所以把这些成本细节写出来是因为很多回测结果好看纯粹是因为忘了算交易成本。一组有代表性的对比结果指标买入持有IF单指数信号策略三因子决策树完整策略年化收益率5.1%6.3%9.2%年化波动21.4%18.6%15.2%夏普比率0.240.340.61最大回撤−33.5%−24.1%−16.8%年换手率0约18倍约12倍这里必须强调这些数字是基于我选定的样本区间和阈值的一组回测结果换成不同参数会有差异但方向是稳定的引入风格因子、情绪因子和状态识别之后收益增厚和回撤收窄同时出现说明这不是单纯靠提高风险换收益。6.3 回测与实盘之间必须衡量的几条鸿沟最后聊几个只有在实盘里才会被放大的问题这也是我一次次在回测与实盘之间碰壁后的体会。第一保证金管理和强平风险。回测里默认账户永远有足够的保证金实盘里如果连续几天出现亏损保证金占用率会迅速上升可能被迫减仓而这又反过来加剧亏损。所以我给策略加了硬约束单日亏损超过账户权益2%时次日强制将IF净敞口降到0.5以下。第二期货贴水与展期成本。沪深300股指期货在大部分时间处于贴水状态尤其临近交割月时基差收敛长期持有IF合约其实要承担持续损耗。回测里按连续主力合约拼接价格上涨但我在实盘处理中必须在每月第三个周五前提前展期展期成本几乎完全吃掉一部分超额收益。这一点直接影响了策略的最终参数持仓周期超过5日的信号才值得开仓过短的信号会被摩擦成本淹没。第三数据时点的前视偏差。我在第2章强调过波动率指数的滞后处理实际上10年期国债收益率也存在类似问题中债估值公布时间在每日17:00左右当天15:00收盘时根本用不上当天的值。如果回测里直接在当天用一个晚上才能拿到数据等于给了策略预知未来的超能力回测曲线再好看也是假的。所有宏观与情绪类特征我都统一在次日生效这才是我认为可以拿来做决策的真实数据结构。这套项目做完最大的收获倒不是最终夏普比率提高了多少而是形成了一套数据→因子→模型→状态→仓位的完整研究范式。以后再加入新的数据源比如北向资金、产业资金数据我只需要把它处理成对齐好的特征塞进决策树里重新训练流程完全复用。如果你也想做类似的研究我的建议是别一上来就追求模型复杂先把四类数据的关系、把每个特征在SPSS和Python里的口径摸清楚再谈建模。数据之间的一致性永远比模型的精巧程度更能决定你策略的真实表现。
返回列表