强化学习中的随机决策森林·综合运用
(3/3)·前面铺垫了理论与单点实验,这篇用完整流程把随机森林塞进强化学习闭环
「空头持仓里的反转触发逻辑」
下面这段处理的是已有 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 仅作参考,实盘需自行回测验证。
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 策略测试器用真实点差回测。
{
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 结构体成员没随库更新同步暴露。
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.