强化学习中的随机决策森林·综合运用
🌲

强化学习中的随机决策森林·综合运用

(3/3)·前面铺垫了理论与单点实验,这篇用完整流程把随机森林塞进强化学习闭环

案例拆解新手友好 第 3/3 篇
很多人在强化学习里直接喂原始行情,却忽略决策树集成的去相关作用,导致策略在样本外抖得厉害。用随机森林做状态价值近似,能把约37%袋外样本变成天然验证集。先想清楚森林和bagging的关系,再谈训练不迟。

「空头持仓里的反转触发逻辑」

下面这段处理的是已有 OP_SELL 持仓时,系统如何根据 ts 阈值做平仓与反手。ts<=0.5 先平掉空单,红色收盘价出场;若 ts 进一步掉到 0.4 以下,则通过 LotsOptimized() 重算手数并反向开多。 [CODE] case OP_SELL: if(ts<=0.5) if(OrderClose(OrderTicket(),OrderLots(),OrderClosePrice(),0,Red)) { updateReward(); if(ts<0.4) { lots=LotsOptimized(); if(OrderSend(Symbol(),OP_BUY,lots,SymbolInfoDouble(_Symbol,SYMBOL_ASK),0,0,0,NULL,OrderMagic,Green)>0) { updatePolicy(ts); }; } } break; } return; } lots=LotsOptimized(); if((ts<0.4) && (OrderSend(Symbol(),OP_BUY,lots,SymbolInfoDouble(_Symbol,SYMBOL_ASK),0,0,0,NULL,OrderMagic,INT_MIN) > 0)) updatePolicy(ts); else if((ts>0.6) && (OrderSend(Symbol(),OP_SELL,lots,SymbolInfoDouble(_Symbol,SYMBOL_BID),0,0,0,NULL,OrderMagic,INT_MIN) > 0)) updatePolicy(ts); return; } double CalculateMamdani() { CopyBuffer(hnd1,0,0,1,arr1); NormalizeArrays(arr1); CopyBuffer(hnd2,0,0,1,arr2); NormalizeArrays(arr2); CopyBuffer(hnd3,0,0,1,arr3); NormalizeArrays(arr3); if(!random_policy) { vector[0]=arr1[0]; vector[1]=arr2[0]; vector[2]=arr3[0]; CDForest::DFProcess(RDF,vector,RFout); updateNeutral.B(RFout[0]); } else [/CODE] 逐行拆解:case OP_SELL 进入空单分支;ts<=0.5 满足才允许平仓。OrderClose 用当前票号、手数、收盘价、滑点0、红色箭头平仓。平仓成功后 updateReward 刷新收益统计。ts<0.4 时 LotsOptimized 重算手数,OrderSend 以卖价开多(OP_BUY 用 SYMBOL_ASK),魔法码 Green,成功则 updatePolicy 更新策略参数。 无持仓时 lots 重算后,ts<0.4 开多、ts>0.6 开空,魔法码用 INT_MIN 区分。CalculateMamdani 从三个指标句柄拷贝最新1根缓冲,归一化后送入随机森林 DFProcess,输出 RFout[0] 更新中性状态——外汇与贵金属波动剧烈,阈值 0.4 / 0.6 仅作参考,实盘需自行回测验证。

MQL5 / C++
case OP_SELL:
   if(ts<=class="num">0.5)
   if(OrderClose(OrderTicket(),OrderLots(),OrderClosePrice(),class="num">0,Red))
     {
      updateReward();
      if(ts<class="num">0.4)
       {
        lots=LotsOptimized();
        if(OrderSend(Symbol(),OP_BUY,lots,SymbolInfoDouble(_Symbol,SYMBOL_ASK),class="num">0,class="num">0,class="num">0,NULL,OrderMagic,Green)>class="num">0)
         {
          updatePolicy(ts);
         };
       }
     }
    class="kw">break;
   }
class="kw">return;
 }
lots=LotsOptimized();
if((ts<class="num">0.4) && (OrderSend(Symbol(),OP_BUY,lots,SymbolInfoDouble(_Symbol,SYMBOL_ASK),class="num">0,class="num">0,class="num">0,NULL,OrderMagic,INT_MIN) > class="num">0))
   updatePolicy(ts);
 else if((ts>class="num">0.6) && (OrderSend(Symbol(),OP_SELL,lots,SymbolInfoDouble(_Symbol,SYMBOL_BID),class="num">0,class="num">0,class="num">0,NULL,OrderMagic,INT_MIN) > class="num">0))
   updatePolicy(ts);
 class="kw">return;
}
class="type">class="kw">double CalculateMamdani()
 {
  CopyBuffer(hnd1,class="num">0,class="num">0,class="num">1,arr1);
  NormalizeArrays(arr1);
  CopyBuffer(hnd2,class="num">0,class="num">0,class="num">1,arr2);
  NormalizeArrays(arr2);
  CopyBuffer(hnd3,class="num">0,class="num">0,class="num">1,arr3);
  NormalizeArrays(arr3);
  if(!random_policy)
   {
    vector[class="num">0]=arr1[class="num">0];
    vector[class="num">1]=arr2[class="num">0];
    vector[class="num">2]=arr3[class="num">0];
    CDForest::DFProcess(RDF,vector,RFout);
    updateNeutral.B(RFout[class="num">0]);
   }
  else

把随机扰动喂进模糊推理链

这段片段展示了一个中性模糊项的赋值过程:用 MathRandomUniform(0,1,unierr) 在 [0,1] 区间取均匀分布随机数,写进 updateNeutral 的 B 分量。注释掉的 Print 说明调试时可直接观察该值,实盘前建议先取消注释跑几十根 K 线看分布是否真的均匀。 随后三个输入项 firstTerm、secondTerm、thirdTerm 分别用各自输入数组的首元素 arr1[0]、arr2[0]、arr3[0] 初始化,再压入 Inputs 列表。这里只取 [0] 意味着每根 Bar 仅用最新一个采样点参与模糊计算,若你的信号源本身有滞后,这个写法会放大延迟。 OurFuzzy.Calculate(Inputs) 返回 CList 指针 FuzzResult,取索引 0 节点的 Value 作为输出 res,最后 delete 释放防止内存泄漏。外汇与贵金属波动剧烈,模糊输出仅代表当前样本下的倾向性结论,实际下单前务必在 MT5 策略测试器用真实点差回测。

MQL5 / C++
  {
      class="type">int unierr;
      updateNeutral.B(MathRandomUniform(class="num">0,class="num">1,unierr));
   }
   
   class=class="str">"cmt">//Print(updateNeutral.B());
   firstTerm.SetAll(firstInput,arr1[class="num">0]);
   secondTerm.SetAll(secondInput,arr2[class="num">0]);
   thirdTerm.SetAll(thirdInput,arr3[class="num">0]);
   Inputs.Clear();
   Inputs.Add(firstTerm);
   Inputs.Add(secondTerm);
   Inputs.Add(thirdTerm);
   CList *FuzzResult=OurFuzzy.Calculate(Inputs);
   Output=FuzzResult.GetNodeAtIndex(class="num">0);
   class="type">class="kw">double res=Output.Value();
   class="kw">delete FuzzResult;
   class="kw">return(res);
   }

◍ 单核慢跑才能留下训练轨迹

想让代理在优化器里按顺序训练,必须把测试代理和云服务全关掉,只留一个本地核心跑。前一次训练写进文件的结果会喂给下一次,所以禁用遗传算法、改用慢速完整算法是前提,否则中间过程全丢。 EA 只在新柱开启时接管控制,因此训练与测试统一用开盘价跑,避免未来函数污染。优化器里只放一个可调参数——通过数量(迭代次数),演示时设为 15 次。 回归模型(单输出)的曲线很说明问题:第 0 次运行是随机策略,亏得最狠;第 1 次运行产出最好,之后 14 次基本停滞,增长图在第 15 次后平躺。分类模型把奖励拆成正面/负面两类,表现稳得多,从随机的第 0 次一路爬到第 15 次才在最优附近波动。 拿训练区间外的样本一测,两个模型都严重过拟合。把正则化参数 r 设成 0.25(只用 25% 样本训练),噪声变大、学得差了,但全局过拟合没去掉。这类正则化只适合规则不随时间变的平稳过程;输入端塞了 3 个互相关的振荡器,更是过拟合的明面负因素。外汇与贵金属市场高风险,这类现象只是样本内观察,实盘前须在 MT5 用自己品种重跑验证。

「随机森林近似信号函数的落地边界」

用随机森林去近似指标函数、直接输出买卖信号,本质是把有监督学习里的分类与回归塞进了 EA 的优化回路。和 MT5 云测试器里常规优化相比,这套做法在寥寥几次迭代内就能收敛,不必纠结到底放多少个待优化参数,这是它最实在的优点。 但代价也明摆着:一旦策略框架本身选歪了,模型会像过度优化参数那样迅速过拟合。原文附带的两个 mq5 文件(回归版 18.74 KB、分类版 19.03 KB)只是粗框架,编译前必须先把 MT4Orders 库和改过的 Fuzzy 库补齐,否则连基础环境都起不来。 想往前推一步,可走的缝有几个:把多个隶属函数一起丢进优化、往特征里加更硬的预测因子、换奖励函数、或者养几套互相竞争的模型来摊开解空间。外汇和贵金属杠杆高、滑点跳空频繁,这类自学习 EA 在真实账户上过拟合暴露的概率偏大,先用历史数据跑通再谈实盘。 下面这段 OnTester 里的写文件代码,正是社区里有人报 93 个编译错误的根源——RDF.m_nclasses 在未正确声明时就会被当成未声明标识符抛出。逐行看:前两句若在清除模型或非优化态直接返回;优化态下样本数大于 0 才建森林;随后删掉四个旧 txt 缓存;再开文件写 m_bufsize 正常,写到 m_nclasses 这一行就崩,说明 RDF 结构体成员没随库更新同步暴露。

MQL5 / C++
class="type">class="kw">double OnTester()
  {
   if(clear_model) class="kw">return class="num">0;
   if(MQLInfoInteger(MQL_OPTIMIZATION)==true)
     {
       if(numberOfsamples>class="num">0)
         {
          CDForest::DFBuildRandomDecisionForest(RDFpolicyMatrix,numberOfsamples,class="num">3,class="num">1,number_of_trees,regularization,RDFinfo,RDF,RDF_report);
         }
       
       FileDelete("RDFBufsize"+_Symbol+(class="type">class="kw">string)_Period+".txt",FILE_COMMON);
       FileDelete("RDFNclasses"+_Symbol+(class="type">class="kw">string)_Period+".txt",FILE_COMMON);
       FileDelete("RDFNvars"+_Symbol+(class="type">class="kw">string)_Period+".txt",FILE_COMMON);
       FileDelete("RDFMtrees"+_Symbol+(class="type">class="kw">string)_Period+".txt",FILE_COMMON);
       
       class="type">int filehnd=FileOpen("RDFBufsize"+_Symbol+(class="type">class="kw">string)_Period+".txt",FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_COMMON);
       FileWrite(filehnd,RDF.m_bufsize);
       FileClose(filehnd);
       filehnd=FileOpen("RDFNclasses"+_Symbol+(class="type">class="kw">string)_Period+".txt",FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_COMMON);
       FileWrite(filehnd,RDF.m_nclasses); class=class="str">"cmt">//THIS IS WHERE THE ERROR IS BEING THROWN.
让小布替你跑这套
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到随机森林的袋外误差与特征子集分布,把重复劳动交给小布,你专注决策。

常见问题

有放回抽样时,单个样本未被抽中的概率是(1-1/n)^n,n较大时趋近1/e约37%,所以训练集覆盖约63%源数据,其余作袋外测试。
回归常用特征数n/3,分类常用√n,目的是让各树见到的特征集错开,降低树间相关性。
小布盯盘内置了特征子集与袋外误差的可视化,可对接你导出的树参数做快速诊断,不必手算泛化能力。
森林靠大量弱树投票抵消误差,单树不修剪能加快构建,且bagging已缓解过拟合,修剪反而拖慢集成。
外汇贵金属杠杆高、跳空频繁,模型可能失效,仅作概率参考,实盘前需用多品种多周期回测。