先说个我自己踩坑踩出来的结论:在量化交易里用机器学习最稳的姿势,不是让模型猜下一步涨几个点,而是先让它回答市场当前处于什么结构。随机森林是我在这条路上试了一圈后一直留用的模型,这篇文章就用随机森林做一次市场结构预测的完整复盘,覆盖三分类问题怎么定义、特征和标签怎么构造、训练时最容易犯的错,以及最后怎么把模型输出转成真实仓位。适合刚接触量化的新手,也适合已经写过几版策略但觉得信号不稳、想引入机器学习的朋友。
很多人一开始会把预测目标设成“未来N日收益率”,然后交给机器学习模型去拟合。这方向不是不能做,但实际效果通常很差,因为直接回归收益率的信噪比太低,又容易被极端行情带偏。我的做法是先预测市场状态:上涨趋势、下跌趋势、震荡。这不是用机器替代判断,而是把仓位管理的核心问题前置:市场允许我重仓,还是必须轻仓等待。
1. 为什么用随机森林做市场结构识别,而不是靠均线或深度学习
1.1 市场结构问题为什么值得单独建模
传统技术分析里判断市场结构有一堆指标,比如均线排列、MACD金叉死叉、布林带收口开口,还有ADX这类趋势强度指标。它们不是没有用,而是太依赖参数选择,10日和20日均线在某个品种上行,换个周期换个品种可能就失效。人工经验可以弥补,但没法系统化回测,更没法处理几十个特征同时出现矛盾信号的情况。
把市场结构看成分类问题后,输入特征可以同时包含趋势、波动、量能、时序位置等信息,模型自动学习特征之间的非线性关系。比如“均线多头排列加缩量回踩”和“均线多头排列放巨量滞涨”,前者可能继续上涨,后者可能转震荡,两类情况在一维指标上表现接近,但在多维特征空间里可以被区分。随机森林天然的并行决策树结构,能把这种组合条件自动切分出来,这是它能处理市场结构问题的底层原因。
1.2 随机森林相比其他模型方案的优势
我用随机森林之前,先后试过逻辑回归、SVM、轻量级梯度提升框架LightGBM,也写过隐马尔可夫模型HMM,甚至用PyTorch搭过LSTM。最后在能稳定复现、调参成本低、不轻易过拟合这三个维度上,随机森林胜出。
和逻辑回归比,随机森林不需要特征做严格标准化,量价特征常常量纲差异很大,逻辑回归在原始特征上很难收敛到好结果,标准化过程又容易引入未来信息。做金融时序时必须用滚动窗口计算均值和方差,一旦忘记这一点就会造成特征泄漏。SVM在核函数选择上很敏感,金融数据噪声大,RBF核很容易把噪声也学进决策边界。LSTM这类深度模型对数据长度和算力要求高,样本量不够时,预测效果还不如传统树模型稳定。LightGBM确实快,但对异常值更敏感,容易在小样本区间继续分裂,需要更精细的正则化参数,实际做滚动训练时不如随机森林皮实。
随机森林的另两个独特优势是输出袋外误差OOB Score和特征重要性。OOB Score可以在不单独切验证集的情况下估计模型泛化能力,对样本量有限的行情数据来说很实用。特征重要性则能反向验证特征构造是否合理,比如成交量的重要性长期为零,我会检查是不是量能特征处理出了问题。
1.3 需要提前接受的模型局限
随机森林不是万能的。它做不好外推,如果未来行情出现训练集里从未见过的极端状态,比如流动性骤降导致连续涨跌停,模型预测会失真。它在时间序列上的表现也不如专门处理序列的模型,因为单棵决策树看不到时间顺序,特征里必须手动加入时序信息,比如过去5日收益率、20日波动率变化,否则模型会把历史片段当成独立的点来处理。
知道这些局限,使用时不神话它,预测概率只当作仓位参考而不是确定性信号,这是这套策略能跑下去的前提。
2. 标签定义和特征工程决定策略上限
2.1 三分类标签怎么定才合理
市场结构是连续变化的,但仓位管理必须离散化。我把标签设成三类:上涨、下跌、震荡。问题在于阈值怎么选。固定百分比阈值,比如未来10日涨幅超过2%算上涨、低于-2%算下跌、中间算震荡,在不同波动环境下会有偏差。2015年那种行情一天波动就超过2%,而在低波动年份,一个月都到不了2%。结果会是高波动时期几乎没有震荡样本,低波动时期几乎没有趋势样本。
改用自适应阈值会好很多。核心思路是用历史波动率ATR归一化收益,再定阈值。标签计算公式:未来N日累计收益除以同期平均真实波幅ATR,大于0.5算上涨,小于-0.5算下跌,中间算震荡。这样定义出的标签在不同市场环境下分布相对均衡,模型学到的是“相对波动程度的趋势”,而不是“固定价格的涨跌”,更接近交易员说的趋势和震荡。
N的取值也要说明。我用的是未来5个交易日,因为更长的预测周期虽然信号更稳,但特征和标签之间的时间跨度太大,中间行情反转会让标签极不准确。5日短周期适合做波段频率,回撤相对可控。
2.2 特征构造的四个层次
特征不是越多越好。随机森林在高维特征下也有过拟合风险,而且大量无关特征会稀释真正重要特征的影响力。我按四个层次构造原始特征池,每个层次代表市场的一个观测角度。
第一层是价格结构特征,包括均线乖离率、短期均线与长期均线比值、布林带位置、ADX趋势强度。这层回答趋势是否明确。第二层是动量特征,包含不同周期的收益率、动量震荡指标如RSI、MACD柱状值。这层回答上涨下跌的动能是否持续。第三层是波动特征,例如ATR与价格比值、历史波动率、真实波幅的滚动分位数。这层回答当前波动环境是适合趋势策略还是震荡策略。第四层是量能特征,比如成交量均线比值、OBV能量潮的斜率、量价相关性。这层回答价格变化有没有资金认同。
实际写入模型的不是全部特征,而是先做相关性筛选。相关系数高于0.8的两两特征只保留其中一个,然后用OOB特征重要性排序取前40到60个。这样既保留了信息维度,又减少冗余。
2.3 最容易毁掉策略的数据泄漏问题
数据泄漏是机器学习量化策略中最隐蔽的坑。最常见的错误是用未来数据计算特征。比如计算某一天的20日均线,只能使用当天及之前的数据,但如果代码中用shift方法时方向弄反,等于把未来几天的平均价格带入样本。表面回测很漂亮,实盘一塌糊涂,原因就在这里。
还有两种更隐蔽的泄漏。一种是特征标准化时用了全样本均值和标准差,正确做法是只在训练集上计算参数,再应用到验证集和测试集。另一种是样本划分时直接随机打乱,金融时序必须按时间顺序切分,否则模型会用未来行情训练再用过去行情验证,相当于提前看到了答案。后面讲回测时还会强调这一点。
3. 随机森林建模过程与参数调优实战
3.1 数据集划分和样本不平衡处理
我以日线数据为主,每只股票取最近8年日线样本,按时间顺序划分为训练集、验证集、测试集,比例大致是6比2比2。训练集不参与验证集调参,验证集用于选择超参数,测试集只在最终模型上跑一次。如果反复使用测试集调参,测试集就失去了评估意义。
一个现实问题是三分类样本通常不平衡,震荡样本往往最多,其次是上涨和下跌。这种情况下模型会倾向预测震荡,因为整体准确率更高,但这不符合交易需求。我没有用SMOTE做少数类过采样,因为金融时序样本之间不是完全独立,插值会创造现实中不存在的价格路径。更好的方案是给少数类更高的样本权重,或者用class_weight参数调整权重,同时在评估时不只看准确率,还看各类别的精确率和召回率。
3.2 核心超参数怎么调
随机森林的超参数不算多,但每个参数的直觉必须清楚。我按优先级顺序讲,实际操作时可以少走弯路。
n_estimators是树的数量。很多教程说越大越好,但实际中超过500棵后精度提升极其有限,训练时间线性增长。我通常先设500棵观察收敛曲线,如果验证集误差还在下降就继续增加,如果已经平稳就降回200到300棵。对我常用的中小规模数据集,200到300棵是性价比最高的范围。
max_depth控制树的深度。随机森林本身通过随机抽样降低了树之间的相关性,但每棵树仍然可能过深。经典经验是限制深度在10到20层,具体数值要靠验证集调。深度太浅欠拟合,无法捕捉特征组合;深度太深则每棵树的叶子节点只包含极少数样本,预测方差变大。
min_samples_leaf叶子节点最小样本数可能是最值得优先调试的参数。默认值为1时,理论上树可以无限细分到每个样本一片叶子。加上这个参数后,每个叶子至少需要一定样本量,预测结果是组内平均,稳定性大幅提高。我常用值在20到100之间,具体取决于训练集规模。叶子节点样本量越大,模型越平滑,能有效压制噪声交易日的干扰。
max_features每次分裂时随机抽取的特征数。随机森林的随机性主要来自这里。默认值是特征总数的平方根,对于分类问题通常够用。调参方向是:特征强相关时设小一些,让树更随机;特征间相关性较弱时设大一些。我个人测试下来,0.6到0.8倍的特征数效果比默认值略好,但对结果的影响不像叶子节点样本数那么明显。
3.3 滚动训练策略与模型更新周期
市场结构有状态迁移的特征,直接用8年前的老数据训练模型去预测今天的状态,效果会不断衰减。我的处理是做滚动训练:每20个交易日重新训练一次模型,训练窗口保持最近的3年数据,验证集用最近3个月。这样每月份模型看到的都是大约3年左右的市场环境。
为什么不用更长的训练窗口?因为更早的数据虽然增加了样本量,但也让模型更偏向久远的历史规律,而市场微观结构变化很快。3年在样本量和时效性之间比较平衡。为什么20个交易日重训一次?因为如果每日重训,特征和标签变化很小,计算资源却成倍消耗。20天覆盖一个月左右的交易周期,基本跟市场状态变化频率匹配。
训练代码的写法和普通机器学习项目差别不大,关键点在于流程闭环。训练前先确认标签区间已结束,当前日期到标签截止日之间不能包含未来数据;训练后要把OOB Score和验证集混淆矩阵记录下来,作为该期模型质量的基线,方便后续对比。
4. 从预测概率到真实交易决策的完整链路
4.1 概率输出必须做校准,不能直接用
随机森林输出的概率并不是严格意义上的概率,它只是投票树中预测该类别树的比例。不同树之间相关性高时,概率会被推向两端,比如经常出现0.9以上的高置信预测,但实际准确率只有六成。直接拿这个值触发交易,会造成频繁止损。
我使用等渗回归Isotonic Regression做概率校准。校准方法不复杂,用验证集上模型的原始输出和真实标签,拟合一个单调映射函数,让校准后的概率和真实频率更接近。校准做完后再检查可靠性曲线,最理想的情况是预测0.7的样本实际上也有70%左右上涨。实际做下来,经过校准后的概率比原始概率更平滑,阈值触发也更稳定。
4.2 三分类概率如何映射为仓位配置
模型给每个交易日输出三个概率,P_up上涨概率、P_down下跌概率、P_side震荡概率。把它们直接相加等于1。单纯的分类准确率不是交易目标,我把它转换成多空综合得分,计算公式为:Score等于上涨概率乘以1加震荡概率乘以0加下跌概率乘以负1,所以Score的取值为负1到1之间。实际用的时候,如果只做多头,只看Score大于正0.15才考虑买入,Score低于负0.1就清仓。中间地带不操作。
为什么阈值不对称?因为下跌时的容错空间小,宁可少赚也不要扛单。这个阈值参数也要放在验证集上滚动优化,不能拍脑袋固定。另一个细节是交易信号确认。单日模型输出波动很大,我会要求连续两天Score同方向才触发,然后用次日开盘价成交。多等一天虽然信号稍有滞后,但换来了更低的虚假信号率,实测夏普比率更高。
4.3 资金管理和止损规则
市场结构预测解决的是方向问题,资金管理解决的是活命问题。模型预测上涨趋势也不意味着可以满仓。我给每笔交易的资金占用设了上限,单笔亏损控制在总资金的1%以内,也就是如果止损位设置5%,仓位就不能超过总资金的20%。
止损位不按固定百分比,而是按ATR的倍数计算,两倍ATR为止损距离。如果行情波动放大,止损距离跟着放大,仓位就需要等比缩小。这保证了每笔交易的风险金额恒定,不会因为某段时间行情波动变大而承受超出预期的亏损。具体执行中我还会把Score值分档,Score越高初始仓位越重,但最大不超过账户资金的40%,剩余资金作为加仓的子弹。
5. 回测框架搭建和常见的过拟合陷阱
5.1 Walk-Forward回测为什么是必须的
普通K折交叉验证对时间序列不适用,原因是样本之间有自相关性,随机切分会把未来样本混入训练集。金融时序必须用Walk-Forward渐进式回测,过程描述起来比较直白:在第一个窗口训练模型,预测下一个窗口,记录预测结果;然后窗口向后滚动一个周期,重新训练,预测下一个窗口,不断重复直到覆盖全部历史。这样每一笔预测在发生时都不包含未来信息,检验的是模型在真实未知数据上的表现。
我用Python实现Walk-Forward回测时,一开始想省事,自己手动写了窗口切片逻辑,结果在索引偏移上出了不少问题。后来改用现成的金融回测框架,或者用简单的循环加DataFrame索引切片实现。核心代码量其实不大,重点在于每个滚动周期结束时,评估指标必须按时间顺序拼接成一条完整的资金曲线,而不是把每期结果简单平均,因为交易是有连续性的。
5.2 回测中必须计入的成本项
机器学习策略最常见的问题是换手率极高,特征微小变化导致信号频繁翻转,模型每周把仓位翻来覆去,手续费和滑点会吞掉大部分收益。因此回测时手续费和滑点必须往宽里算。至少按双边千分之二计算交易成本,并加入固定滑点,比如每笔成交相对信号价格偏离5个基点。如果扣完这些成本后策略仍然盈利,再考虑是否进入实盘,这样比较稳妥。
除了交易成本,还要考虑不可成交情形。涨跌停板时信号可能完全无法成交,停牌时资金占用无法释放。这些在回测系统里很难完全模拟,我会做保守处理:如果信号触发当天开盘即涨停,放弃这笔买入;如果持仓股连续跌停,止损单也成交不了,只能假设在恢复交易的次日收盘价离场。这种极端情况虽然损失模拟不够精确,但至少不会让资金曲线看起来虚高。
5.3 评价指标不只是夏普和收益
看过太多策略报告用高年化收益、高夏普吸引眼球,但资金曲线稍微分析就能看到问题。我会额外关注三个指标:最大回撤是否在心理承受范围内,卡玛比率即年化收益除以最大回撤是否大于1,以及月度正收益比例是否稳定。最大回撤尤其重要,因为实际交易中最大的敌人不是模型,是回撤期能否拿住仓位。如果策略历史最大回撤30%,按资金管理规则单笔止损不大于1%,实际运行中出现20%回撤已经说明风控失效,必须找原因而不是硬扛。
胜率与盈亏比也要分开看。三分类模型胜率在50%左右,但盈利单平均盈利幅度大于亏损单平均亏损幅度,策略依然可以赚钱。关键是把每次止损上限先定住,才能让盈亏比自然体现出来。我见过太多人在亏损单上不肯止损,却在盈利单上过早离场,最终导致高胜率低盈亏比,长期微赚甚至亏损。这点在执行层面比模型精度更重要。
6. 常见问题速查:随机森林策略从训练到实盘遇到的坑
6.1 训练集精度很高,验证集精度却很差
八成是过拟合。先检查是不是叶子节点样本数设得太小,这是随机森林过拟合的第一大原因。其次检查特征数量是否过多,如果特征池超过100个,建议先做相关性筛选和特征重要性排序。最后检查是否在同一个数据集上反复调参太久,导致验证集信息间接进入了模型选择过程。解决方法是扩大训练样本、增加叶子节点样本数,再用滚动训练机制保证模型只学习长期稳定的规律。
6.2 训练好的模型长期预测震荡,几乎没有交易信号
通常是标签不平衡导致。震荡类样本占比过高,模型学到的先验概率偏向震荡。处理优先级先看标签定义阈值是否合理,可以适当调窄震荡区间,让更多样本落入趋势区间。再看是否用class_weight调整类别权重,给上涨和下跌类提高权重。如果模型仍然长期预测震荡,还要检查特征是否有效,比如ADX或者动量特征在震荡区间是否区分度不足,需要做特征迭代。
6.3 概率校准后分数阈值难以确定
阈值的选择本身就是和风险偏好绑定的事情,不存在通用最优值。实际操作中把校准后的Score在验证集上按0.01粒度扫描,每个阈值都计算资金曲线的卡玛比率,选择卡玛比率最高的阈值。但这个选择也必须纳入Walk-Forward流程,不能直接用同一份验证集的最高点去反推。阈值确定后在测试集上一次性验证,如果测试结果和验证结果差异过大,就要考虑是不是阈值定得太精细,太贴合某段行情了。
6.4 在不同品种上回测结果差异极大
随机森林没有学会普适的规律,而是记住了某个品种特有的价格行为模式。单品种模型天然有这个问题。解决方向有两个方向:一是用全市场多个品种的合并数据训练一个通用模型,让样本量更大,规律更泛化,但需要先做横截面特征标准化;二是坚持做单品种模型,但必须接受换品种后需要重新训练,不能把一个品种的模型参数直接套到另一个品种。
6.5 换手率太高导致交易成本侵蚀利润
这是机器学习策略高频常见的痛楚。模型每根K线都在重新评估,特征轻微变化就让输出概率跳变,信号频繁翻转。我做了几个有效处理:一是信号确认机制,连续两日同向才触发;二是加滞后惩罚,买卖反向切换时需要Score超过更高阈值才允许反手;三是降低调仓频率,模型输出只作为每日收盘评估依据,不是频繁做日内翻转。回测中对比加约束前后的换手率,通常会下降40%以上,而单笔交易质量明显提升。
6.6 特征重要性排名频繁变化说明什么
如果每次训练特征重要性排名都在剧烈变化,说明特征之间存在较强的相关性,模型在多个特征之间随机选择时分配重要性不稳定。先做相关性去除,把高度相关的分组中只保留一个代表特征。再看样本量是否不足,树在少量样本上对特征选择更敏感。最后提醒自己,特征重要性只反映特征在训练数据上的贡献度,不直接等于预测能力,更不代表因果关系。一个重要性持续很高的特征,也可能是某种市场噪声被放大了,需要在不同时间窗口下做交叉验证。
7. 这套策略的后续扩展方向和我的实际操作体会
如果完整跑通了这套流程,后面可以做的扩展方向有几个。一个方向是做多标的组合,用同一套模型对几十个品种打分,然后按Score排名持有多头组合,分散单品种风险。另一个方向是引入更丰富的替代数据,比如资金流、不同期限的期货基差、波动率曲面数据,让市场结构判断跳出单纯量价范畴。还有一个方向是把三分类概率作为仓位控制信号,叠加到你原有的选股或事件驱动策略上,它不直接选标的,而是帮助判断当前市场适不适合放开风险敞口。
我个人在实际操作中还有一个体会:不要把随机森林的输出当成每天都要做决策的触发器,更多把它当成一个过滤器。模型说市场处于下跌结构时,即使某些个股出现买点,也可以放弃或者只用极小仓位试探。模型说市场结构转为上涨时,再放大选股条件去筛选标的。这套组合使用的思路,比单独依赖模型做每日多空决策要踏实得多。
另外想提醒大家,任何策略在实盘前都要经历一个阶段,我习惯称之为模拟盘校准期:模型信号先持续跟踪一两个月,严格记录信号出现频率、准确度、滑点情况。这个阶段没有资金压力,可以静下心看模型输出是否和真实行情节奏一致。很多模型问题,比如信号太滞后、震荡市反复打脸,都是在模拟盘阶段暴露出来的。不要急着上真金白银,这一步能省掉后面很多痛苦。
最后,这套策略代码量不算大,核心是特征和标签的业务理解,以及训练和回测的逻辑严谨性。只要控制了数据泄漏、坚持滚动训练、做好概率校准和资金管理,用随机森林做市场结构预测完全可以在实盘中成为一个稳定且有价值的辅助模块。但实盘交易没有一劳永逸的策略,模型和参数需要定期维护更新,对各种极端行情的应对预案也同样重要。