您应该知道的 MQL5 向导技术(第 03 部分):香农(Shannon)熵·综合运用
(3/3)·从熵公式推导到策略测试器落地,手把手看信息论如何筛掉低质量信号
优化器里给负信号塞随机噪声
在策略优化阶段,如果信号类算出的是负值,代码不会直接丢弃,而是用均匀随机把结果拆成一对互补概率写回样本矩阵。具体看 CSignalSE::ResultUpdate,当 MQLInfoInteger(MQL_OPTIMIZATION) 为真且 Result<0.0 时,取 MathRandomUniform(0,1) 的随机数 _odds,分别填进 DF_SIGNAL 最后一行的第 __INPUTS 与 __INPUTS+1 位,两者相加恒为 1.0。 OnTick 里先判断 m_read_forest 标志,未读森林就 WriteForest(),再交给 ExtExpert.OnTick() 跑常规逻辑;OnTester 则在回测结束时 ReadForest() 并返回 0.0,这两段是把熵信号类接进 EA 事件循环的桥。 这类写法意味着:优化器看到的负信号样本,实际是被随机化过的分布,而非原始熵值。在 MT5 里跑遗传优化时,若你发现样本尾部出现大量 0~1 之间的对称填充,大概率就是这段在起作用,可据此判断是否要改阈值或换填充策略。外汇与贵金属杠杆高,任何信号填充逻辑都只是概率参考,实盘前务必用历史数据自检。
class="kw">return(_result); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSignalSE::ResultUpdate(class="type">class="kw">double Result) { if(MQLInfoInteger(MQL_OPTIMIZATION)) { class="type">int _err; if(Result<class="num">0.0) { class="type">class="kw">double _odds = MathRandomUniform(class="num">0,class="num">1,_err); class=class="str">"cmt">// DF_SIGNAL[m_samples-class="num">1].Set(__INPUTS,_odds); DF_SIGNAL[m_samples-class="num">1].Set(__INPUTS+class="num">1,class="num">1-_odds); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| "Tick" event handler function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { if(!signal_se.m_read_forest) signal_se.WriteForest(); ExtExpert.OnTick(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| "Tester" event handler function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double OnTester() { signal_se.ReadForest(); class="kw">return(class="num">0.0); } class=class="str">"cmt">// wizard description start class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Description of the class | class=class="str">"cmt">//| Title=Trading with &class="macro">#x27;Shannon Entropy&class="macro">#x27; optimized trade volume | class=class="str">"cmt">//| Type=Money | class=class="str">"cmt">//| Name=SE | class=class="str">"cmt">//| Class=CMoneySE | class=class="str">"cmt">//| Page=money_se | class=class="str">"cmt">//| Parameter=ScaleFactor,class="type">int,class="num">3,Scale factor |
◍ 把香农熵条件塞进仓位类
这段实现了一个叫 CMoneySE 的仓位管理类,它继承自 CExpertMoney,核心思路是用信号层的「绝对条件值」去动态修正开仓手数。类里只暴露两个写接口:ScaleFactor 设缩放整数,AbsoluteCondition 直接吃 signal_se.m_last_condition 的绝对值。 全局层把 CExpert、CSignalSE 指针和 CMoneySE 指针都挂了出来,OnTick 里先做一道判断:如果随机森林还没读入就先 WriteForest(),随后把最新条件的绝对值喂给 money_se,再跑专家对象的 OnTick()。 Optimize 函数是手数修正的落点,入参 lots 为基础手数,注释写明要「按条件量级归一化」——也就是说条件越强,手数倾向放大,但具体系数由 m_scale_factor 控制。外汇与贵金属杠杆高,这种按信号强度加仓的做法可能放大回撤,上 MT5 前先把 scale_factor 设为 1 做小样本验证。 别把归一化当护身符 条件绝对值大只代表模型当前置信度高,不意味着价格必然朝头寸方向走;仓位随条件放大时,滑点和点差在贵金属跳空时段可能吃掉大部分预期优势。
class=class="str">"cmt">//| Parameter=Percent,class="type">class="kw">double,class="num">10.0,Percent | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// wizard description end class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CMoneySE. | class=class="str">"cmt">//| Purpose: Class of money management with &class="macro">#x27;Shannon Entropy&class="macro">#x27; optimized volume. | class=class="str">"cmt">//| Derives from class CExpertMoney. | class=class="str">"cmt">//+------------------------------------------------------------------+ class CMoneySE : class="kw">public CExpertMoney { class="kw">protected: class="type">int m_scale_factor; class="kw">public: class="type">class="kw">double m_absolute_condition; CMoneySE(class="type">void); ~CMoneySE(class="type">void); class=class="str">"cmt">//--- class="type">void ScaleFactor(class="type">int scale_factor) { m_scale_factor=scale_factor; } class="type">void AbsoluteCondition(class="type">class="kw">double absolute_condition) { m_absolute_condition=absolute_condition; } class="kw">virtual class="type">bool ValidationSettings(class="type">void); class=class="str">"cmt">//--- class="kw">virtual class="type">class="kw">double CheckOpenLong(class="type">class="kw">double price,class="type">class="kw">double sl); class="kw">virtual class="type">class="kw">double CheckOpenShort(class="type">class="kw">double price,class="type">class="kw">double sl); class="kw">protected: class="type">class="kw">double Optimize(class="type">class="kw">double lots); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global expert object | class=class="str">"cmt">//+------------------------------------------------------------------+ CExpert ExtExpert; CSignalSE *signal_se; CMoneySE *money_se; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| "Tick" event handler function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { if(!signal_se.m_read_forest) signal_se.WriteForest(); money_se.AbsoluteCondition(fabs(signal_se.m_last_condition)); ExtExpert.OnTick(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Optimizing lot size for open. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CMoneySE::Optimize(class="type">class="kw">double lots) { class="type">class="kw">double lot=lots; class=class="str">"cmt">//--- normalize lot size based on magnitude of condition
「连续亏损时自动砍仓位的手数逻辑」
这段手数优化函数把「连续亏损」当成减仓触发器:从成交历史倒序遍历,只统计当前品种、且利润小于 0 的连续亏损笔数,一旦 losses>1,就把 lot 乘以 m_scale_factor 再除以 (losses+m_scale_factor),最后保留两位小数。 代码先调用 HistorySelect(0,TimeCurrent()) 拉全量历史,用 CDealInfo 逐笔取 Profit(),遇到盈利单直接 break,所以只认「 uninterrupted 连亏」。若 deal.Ticket() 返回 0 会打印无历史并跳出循环,实盘第一次跑前务必先有成交记录。 减仓后还要过三道合规:stepvol 取整到平台 LotsStep 倍数;低于 LotsMin 拉回最小手数;超过 LotsMax 压回上限。外汇与贵金属杠杆高,连亏后盲目维持原手数可能加速回撤,这套逻辑倾向降低尾部风险,但是否生效需在 MT5 策略测试器用真实点差复盘验证。 让小布替你跑这套 把 m_scale_factor 设成 0.5 丢进 EURUSD 的 2023 年 Tick 回测,观察连亏 3 笔后手数是否降到原计划的约 1/5,再决定是否上实盘。
lot*=(class="num">20*m_scale_factor/fmax(class="num">20.0,((class="num">100.0-m_absolute_condition)/class="num">100.0)*class="num">20.0*m_scale_factor*m_scale_factor)); class=class="str">"cmt">//--- reduce lot based on number of losses orders without a class="kw">break if(m_scale_factor>class="num">0) { class=class="str">"cmt">//--- select history for access HistorySelect(class="num">0,TimeCurrent()); class=class="str">"cmt">//--- class="type">int orders=HistoryDealsTotal(); class=class="str">"cmt">// total history deals class="type">int losses=class="num">0; class=class="str">"cmt">// number of consequent losing orders CDealInfo deal; class=class="str">"cmt">//--- for(class="type">int i=orders-class="num">1;i>=class="num">0;i--) { deal.Ticket(HistoryDealGetTicket(i)); if(deal.Ticket()==class="num">0) { Print("CMoneySE::Optimize: HistoryDealGetTicket failed, no trade history"); class="kw">break; } class=class="str">"cmt">//--- check symbol if(deal.Symbol()!=m_symbol.Name()) class="kw">continue; class=class="str">"cmt">//--- check profit class="type">class="kw">double profit=deal.Profit(); if(profit>class="num">0.0) class="kw">break; if(profit<class="num">0.0) losses++; } class=class="str">"cmt">//--- if(losses>class="num">1){ lot*=m_scale_factor; lot/=(losses+m_scale_factor); lot=NormalizeDouble(lot,class="num">2);} } class=class="str">"cmt">//--- normalize and check limits class="type">class="kw">double stepvol=m_symbol.LotsStep(); lot=stepvol*NormalizeDouble(lot/stepvol,class="num">0); class=class="str">"cmt">//--- class="type">class="kw">double minvol=m_symbol.LotsMin(); if(lot<minvol){ lot=minvol; } class=class="str">"cmt">//--- class="type">class="kw">double maxvol=m_symbol.LotsMax(); if(lot>maxvol){ lot=maxvol; } class=class="str">"cmt">//--- class="kw">return(lot); }
回测亮眼但别照搬
策略测试器里跑出来的数字确实好看:第一套智能系统优化后盈利因子 2.89、锋锐比率 4.87;第二套更猛,盈利因子 3.65、锋锐比率 5.79。这两个值都来自文末附的 EA 在 4 小时图开盘价上的优化结果,属于可复现的回测数据点。 不过这套 EA 只在 4H 开盘价优化止损止盈,意味着你在实盘甚至测试器每跳模式下都大概率复制不出同样曲线。外汇和贵金属杠杆高、滑点跳空频繁,盲目追这种数值极可能亏穿。 代码里森林推演就一行,把输入输出矩阵丢进决策森林算完,再把第二列结果写进更新缓冲: CDForest::DFProcess(DF,m_in_calculations,m_out_calculations); m_update.B(m_out_calculations[1]); 真正值得做的,是把文章里的决策森林思路拆出来改自己的信号过滤,而不是等圣杯。行情相关性过重时,自定义出的边锋才可能在高波动里帮你一把。
CDForest::DFProcess(DF,m_in_calculations,m_out_calculations); m_update.B(m_out_calculations[class="num">1]);