您应该知道的 MQL5 向导技术(第 03 部分):香农(Shannon)熵·进阶篇
📊

您应该知道的 MQL5 向导技术(第 03 部分):香农(Shannon)熵·进阶篇

(2/3)· 从球的颜色概率到 K 线波动熵值,多数交易者算错了“无序”的真实刻度

进阶 第 2/3 篇
把行情震荡直接当成“乱”就开仓,是常见误判。熵衡量的是结果的不确定性,不是波动幅度本身。先搞清集合概率与负对数关系,才不会在横盘里把低熵当高熵。

◍ 用随机森林承载香农熵信号

本文落点是香农熵信号,而非随机森林本身,但决策森林类是其基础载体,MQL5 函数库里的决策森林可直接复用。 决策树大家其实都接触过:用价格方向、头尾长度这类属性,把下跌蜡烛和上涨蜡烛拆进不同分支,三跌五涨的样例里,靠「价格暴跌吗」「头比尾长吗」两个问题就能完美划分。 随机森林就是一堆独立决策树投票,得票最多的类作为预测。 关键不在树多,而在树间低相关——不相关的树能互相抵消各自误差,前提是每棵树的预测得比随机强。 两个一千笔一美元订单的系统,和一笔一千美元订单的系统,多数人选前者,因为控制粒度更细,这和森林里多树分散风险一个道理。 森林靠两招压低相关性:打包(bagging)和特征随机性。 打包是带放回抽同样大小 N 的样本,原数据 [U,V,W,X,Y,Z] 可能变成 [U,U,V,X,X,Z],树因此对训练集微小变动不敏感;特征随机性则让每棵树只能看随机子集的特征,传统树选特征1最优,随机树看不到就只能退而求其次,强迫多样化。 信号类里,熵按新近度指数加权、按柱线体量加权,生成值 = 阴线熵 − 阳线熵。 阴线熵更高意味着空头信息更少,表面像趋势跟随危险系统,但权重设计可能弱化这种激进性。 开仓和平仓阈值由计时器触发,类内源函数只在优化时跑。 资金管理类改自「Money Size Optimized」,用 LongCondition/ShortCondition 返回绝对值做仓位比例归一。 森林在每次跳价写入、测试器结束读取,构建训练仅单线程优化。 外汇与贵金属杠杆高,这套信号仅作概率参考,实盘前务必 MT5 回测验证。

MQL5 / C++
class=class="str">"cmt">// wizard description start
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Description of the class                                                        |
class=class="str">"cmt">//| Title=Signals of&class="macro">#x27;Shannon Entropy&class="macro">#x27;                                              |
class=class="str">"cmt">//| Type=SignalAdvanced                                                            |
class=class="str">"cmt">//| Name=Shannon Entropy                                                           |
class=class="str">"cmt">//| ShortName=SE                                                                   |
class=class="str">"cmt">//| Class=CSignalSE                                                                |
class=class="str">"cmt">//| Page=signal_se                                                                |
class=class="str">"cmt">//| Parameter=Reset,class="type">bool,class="kw">false,Reset Training                                      |
class=class="str">"cmt">//| Parameter=Trees,class="type">int,class="num">50,Trees number                                            |
class=class="str">"cmt">//| Parameter=Regularization,class="type">class="kw">double,class="num">0.15,Regularization Threshold                  |
class=class="str">"cmt">//| Parameter=Trainings,class="type">int,class="num">21,Trainings number                                    

「信号类里塞进了决策森林」

把香农熵信号做成可交易信号,核心落点是 CSignalSE 这个类,它从 CExpertSignal 继承,专门负责把熵特征喂给机器学习模块。类里直接挂了 CDecisionForest 对象 DF,以及配套的矩阵 DF_SIGNAL、报告 DF_REPORT 和反馈整型 DF_INFO,相当于把随机森林的前向推理和训练回执都封在信号生成器内部。 输入和输出各用定长数组承接:m_in_calculations 长度由宏 __INPUTS 决定,m_out_calculations 固定为 2,对应二分类式的多空判定。可调参数集中在 m_reset、m_trees、m_regularization、m_trainings 四个成员,分别控制重置开关、树的数量、正则化强度和训练次数;Reset(bool) 方法只改 m_reset,其余靠外部赋值。 在 MT5 里调这类信号,优先看 m_trees 和 m_regularization:树太少易欠拟合,正则化过大倾向把熵波动抹平。外汇与贵金属杠杆高,模型信号仅作概率参考,实盘前务必用策略测试器跑样本外数据。

MQL5 / C++
class CSignalSE : class="kw">public CExpertSignal
  {
   class="kw">public:
     class=class="str">"cmt">//Decision Forest objects.
     CDecisionForest             DF;             class=class="str">"cmt">//Decision Forest
     CMatrixDouble               DF_SIGNAL;      class=class="str">"cmt">//Decision Forest Matrix for inputs and output
     CDFReport                   DF_REPORT;      class=class="str">"cmt">//Decision Forest Report for results
     class="type">int                         DF_INFO;        class=class="str">"cmt">//Decision Forest feedback
     class="type">class="kw">double                      m_out_calculations[class="num">2], m_in_calculations[__INPUTS]; class=class="str">"cmt">//Decision Forest calculation arrays
     class=class="str">"cmt">//--- adjusted parameters
     class="type">bool                        m_reset;
     class="type">int                         m_trees;
     class="type">class="kw">double                      m_regularization;
     class="type">int                         m_trainings;
     class=class="str">"cmt">//--- methods of setting adjustable parameters
     class="type">void                        Reset(class="type">bool value){ m_reset=value; }

决策森林信号类的成员骨架

这段声明把一个融合决策森林与 Mamdani 模糊系统的信号类 CSignalSE 摊开了。先看三个 setter:Trees() 写树数量到 m_trees,Regularization() 写正则化系数到 m_regularization,Trainings() 写训练次数到 m_trainings,三者都是 int/double 入参直接赋值,没有边界检查。 模糊系统一侧用指针挂了一组对象:m_fuzzy 是 CMamdaniFuzzySystem 主体,m_in_variables[__INPUTS] 与 m_out_variable 分别管输入输出模糊变量,m_rule[__RULES] 存模糊规则,m_signals[][__INPUTS] 是二维双精度数组缓存信号。__INPUTS 和 __RULES 是编译期宏,改这两个值就能扩维,但数组栈上声明意味着维度过大可能爆栈。 时间相关字段 m_last_time、m_last_signal、m_last_condition 记录了上一次触发的时间与数值,配合 m_update(正态隶属度函数指针)做在线更新。虚函数 LongCondition() 与 ShortCondition() 返回 int,大概率是多空条件评分,由 ValidationSettings() 和 InitIndicators() 先把关指标与时序初始化。 把这套类拷进 MT5 的 Include 目录,把 __INPUTS 设成你特征数(例如 5),编译看 m_signals 维度是否告警,就能验证模糊层接决策森林的内存布局。外汇与贵金属杠杆高,这类模型信号仅作概率参考,实盘前务必用历史数据回测。

MQL5 / C++
class="type">void                Trees(class="type">int value){ m_trees=value; }
class="type">void                Regularization(class="type">class="kw">double value){ m_regularization=value; }
class="type">void                Trainings(class="type">int value){ m_trainings=value; }

class=class="str">"cmt">//Decision Forest FUZZY system objects
CMamdaniFuzzySystem              *m_fuzzy;

CFuzzyVariable                  *m_in_variables[__INPUTS];
CFuzzyVariable                  *m_out_variable;
CDictionary_Obj_Double          *m_in_text[__INPUTS];
CDictionary_Obj_Double          *m_out_text;
CMamdaniFuzzyRule               *m_rule[__RULES];
CList                           *m_in_list;
class="type">class="kw">double                          m_signals[][__INPUTS];

CNormalMembershipFunction   *m_update;

class="type">class="kw">datetime                        m_last_time;
class="type">class="kw">double                          m_last_signal;
class="type">class="kw">double                          m_last_condition;
                                CSignalSE(class="type">void);
                                ~CSignalSE(class="type">void);
class=class="str">"cmt">//--- method of verification of settings
class="kw">virtual class="type">bool                    ValidationSettings(class="type">void);
class=class="str">"cmt">//--- method of creating the indicator and timeseries
class="kw">virtual class="type">bool                    InitIndicators(CIndicators *indicators);
class=class="str">"cmt">//--- methods of checking if the market models are formed
class="kw">virtual class="type">int                     LongCondition(class="type">void);
class="kw">virtual class="type">int                     ShortCondition(class="type">void);
class="type">bool                            m_random;

◍ 随机森林类的熵值累加写法

下面这段类声明把振荡器的状态与随机森林的读写绑在一起:m_read_forest 控制是否加载已训练森林,m_samples 记录样本数,InitSE 负责把指标挂进 CIndicators 容器。Data 方法直接算相邻两根 K 线的收盘价差,等于把动量压成一维特征。 bool m_read_forest; int m_samples; //--- method of initialization of the oscillator bool InitSE(CIndicators *indicators); double Data(int Index){ return(Close(StartIndex()+Index)-Close(StartIndex()+Index+1)); } void ReadForest(); void WriteForest(); void SignalUpdate(double Signal); void ResultUpdate(double Result); double Signal(void); double Result(void); bool IsNewBar(void); 熵的更新段里,_long_entropy 和 _short_entropy 都按信号数 __SIGNALS[i] 做加权:当 _data 大于 0 时减掉一项 (1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(1.0/__SIGNALS[i])/log10(2.0)),小于 0 时走 _short_entropy 的对称分支。log10(2.0) 把底换到 2,熵量纲是 bit。 if(_data>0.0) { _long_entropy-=((1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(1.0/__SIGNALS[i])/log10(2.0))); } else if(_data<0.0) { _short_entropy-=((1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(1.0/__SIGNALS[i])/log10(2.0))); } if(_data>0.0) { _long_entropy-=((1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(1.0/__SIGNALS[i])/log10(2.0))); } else if(_data<0.0) { 注意原文里 _long_entropy 的累加被写了两次(两个 if(_data>0.0) 块),若直接编译会让多头熵被重复扣除,复制前先把第二个重复块删掉。外汇与贵金属杠杆高,这类特征工程错误可能放大信号偏移,上 MT5 跑前先单步看熵值符号。

MQL5 / C++
class="type">bool               m_read_forest;
class="type">int                m_samples;
class=class="str">"cmt">//--- method of initialization of the oscillator
class="type">bool               InitSE(CIndicators *indicators);

 class="type">class="kw">double             Data(class="type">int Index){ class="kw">return(Close(StartIndex()+Index)-Close(StartIndex()+Index+class="num">1)); }

class="type">void               ReadForest();
class="type">void               WriteForest();

class="type">void               SignalUpdate(class="type">class="kw">double Signal);
class="type">void               ResultUpdate(class="type">class="kw">double Result);

class="type">class="kw">double             Signal(class="type">void);
class="type">class="kw">double             Result(class="type">void);

class="type">bool               IsNewBar(class="type">void);
      };
            if(_data>class="num">0.0)
            {
               _long_entropy-=((class="num">1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(class="num">1.0/__SIGNALS[i])/log10(class="num">2.0))));
            }
            else if(_data<class="num">0.0)
            {
               _short_entropy-=((class="num">1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(class="num">1.0/__SIGNALS[i])/log10(class="num">2.0))));
            }
            if(_data>class="num">0.0)
            {
               _long_entropy-=((class="num">1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(class="num">1.0/__SIGNALS[i])/log10(class="num">2.0))));
            }
            else if(_data<class="num">0.0)
            {

「定时器里的样本回收与出场利润抓取」

OnTimer 是 EA 生命周期里真正把信号转成样本的行刑场。持仓时若 PositionSelect 命中且filter0.m_last_condition不小于 Signal_ThresholdClose,就调 ResultUpdate 刷新结果;空仓时阈值比对走 SignalUpdate,把当前信号塞进优化样本池。外汇与贵金属杠杆高,这类自动回收逻辑若阈值设错,可能在震荡里频繁误采。

SignalUpdate 内部先判 MQL_OPTIMIZATION:为真才把 m_samples 加一,并把 DF_SIGNAL 矩阵扩到 m_samples 行、__INPUTS+2 列。前 __INPUTS 列写 m_signals[0][i],倒数第二列写 Signal,末列写 1-Signal,相当于给二分类标签留了互补位。 出场利润不是实时算的,而是 HistorySelect(0, m_symbol.Time()) 后倒序扫 HistoryDealsTotal。遇到 DEAL_ENTRY_OUT 的那张ticket,取 DEAL_PROFIT 赋给 _result 并 break——意味着只认最近一次平仓的浮赢落袋值,更早的被忽略。 把这段直接贴进 MT5 策略测试器的 OnTimer,把 Signal_ThresholdOpen/Close 调到 0.15 附近,跑 EURUSD 十五分钟复盘,能直观看到 DF_SIGNAL 行数随信号触发线性增长。

MQL5 / C++
   _short_entropy-=((class="num">1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(class="num">1.0/__SIGNALS[i])/log10(class="num">2.0)));
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Timer" event handler function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
   if(PositionSelect(Symbol()) && Signal_ThresholdClose<=fabs(filter0.m_last_condition))
     {
      filter0.ResultUpdate(filter0.Result());
     }
   class=class="str">"cmt">//
   if(!PositionSelect(Symbol()) && Signal_ThresholdOpen<=fabs(filter0.m_last_condition))
     {
      filter0.SignalUpdate(filter0.m_last_signal);
     }
   ExtExpert.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+  
class="type">void CSignalSE::SignalUpdate(class="type">class="kw">double Signal)
   {
     if(MQLInfoInteger(MQL_OPTIMIZATION))
     {
       m_samples++;
       DF_SIGNAL.Resize(m_samples,__INPUTS+class="num">2);
       
       for(class="type">int i=class="num">0;i<__INPUTS;i++)
       {
         DF_SIGNAL[m_samples-class="num">1].Set(i,m_signals[class="num">0][i]);
       }
       class=class="str">"cmt">//
       DF_SIGNAL[m_samples-class="num">1].Set(__INPUTS,Signal);
       DF_SIGNAL[m_samples-class="num">1].Set(__INPUTS+class="num">1,class="num">1-Signal);      
     }
   }
     if(HistorySelect(class="num">0,m_symbol.Time()))
     {
       class="type">int _deals=HistoryDealsTotal();
       
       for(class="type">int d=_deals-class="num">1;d>=class="num">0;d--)
       {
         class="type">class="kw">ulong _deal_ticket=HistoryDealGetTicket(d);
         if(HistoryDealSelect(_deal_ticket))
         {
           if(HistoryDealGetInteger(_deal_ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT)
           {
             _result=HistoryDealGetDouble(_deal_ticket,DEAL_PROFIT);
             class="kw">break;
           }
         }
       }
     }
   
交给小布盯盘算熵值
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时熵曲线,你只管判断高熵突破的概率倾向。

常见问题

熵与信息成反比,种类越多选择前未知越大;独立事件概率连乘随种类分散迅速变小,取负对数后才得到更大的熵值。
小布提供的是熵诊断而非买卖指令。外汇贵金属属高风险品种,高熵仅代表不确定性倾向增加,需结合价格行为确认。
以 2 为底得到比特单位,以 e 为底得到纳特,仅量纲差异;向导中按信息论惯例选 2 更直观。
低熵代表状态可预测性高,但突破方向仍受外部流动性和新闻影响,只能说概率上蓄势倾向更强。