从基础到中级:结构(五)·进阶篇
📘

从基础到中级:结构(五)·进阶篇

第 2/2 篇

◍ 用结构体和自定义函数算均值与中位数

在 MT5 里处理一组样本价格或指标序列时,直接写死数组计算不利于复用。把数据装进一个 struct,再配两个独立函数分别算平均和中间值,是更干净的做法。 下面这段示例用了两组数据:H 是 7 个汇率观测值 {2.05, 1.97, 1.87, 1.75, 1.99, 2.01, 1.83},K 是 5 个整数 {12, 4, 7, 23, 38}。Average 对 H 求和后除以 7,得到约 1.924;Median 对 K 排序后取第 3 位,得到 12。 中位数函数里有个细节:用位运算 Tmp.Size() & 1 判断奇偶,偶数时取中间两数均值,奇数时直接取下标中间值。这种写法比调用内置函数更透明,也方便你接自己的权重逻辑。外汇和贵金属波动大,这类统计只描述已发生样本,不预示后续方向,实盘需自担高风险。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct st_Data
{
   class="type">class="kw">double Values[];
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double Average(const st_Data &arg)
{
   class="type">class="kw">double sum = class="num">0;
   for(class="type">uint c = class="num">0; c < arg.Values.Size(); c++)
      sum += arg.Values[c];
   class="kw">return sum / arg.Values.Size();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double Median(const st_Data &arg)
{
   class="type">class="kw">double   Tmp[];
   ArrayCopy(Tmp, arg.Values);
   ArraySort(Tmp);
   if(!(Tmp.Size() & class="num">1))
   {
      class="type">int i = (class="type">int)MathFloor(Tmp.Size() / class="num">2);
      class="kw">return (Tmp[i] + Tmp[i - class="num">1]) / class="num">2.0;
   }
   class="kw">return Tmp[Tmp.Size() / class="num">2];
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define PrintX(X) Print(class="macro">#X, " => ", X)
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart(class="type">void)
{
   const class="type">class="kw">double H[] = {class="num">2.05, class="num">1.97, class="num">1.87, class="num">1.75, class="num">1.99, class="num">2.01, class="num">1.83};
   const class="type">class="kw">double K[] = {class="num">12, class="num">4, class="num">7, class="num">23, class="num">38};
   st_Data Info_1,
           Info_2;
   ArrayCopy(Info_1.Values, H);
   PrintX(Average(Info_1));
   ArrayCopy(Info_2.Values, K);
   PrintX(Median(Info_2));
}

正文

<span class="number">03</span>. <span class="comment">//+------------------------------------------------------------------+</span> <span class="number">04</span>. <span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt; <span class="number">05</span>. <span class="keyword">struct</span> st_Data <span class="number">06</span>. { <span class="number">07</span>.&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">double</span> Values[]; <span class="number">08</span>. }; <span class="number">09</span>. <span class="comment">//+------------------------------------------------------------------+</span> <span class="number">10</span>. <span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt; <span class="number">11</span>. <span class="keyword">double</span> Average(<span class="keyword">const</span> st_Data &lt;T&gt; &amp;arg) <span class="number">12</span>. { <span class="number">13</span>.&nbsp;&nbsp;&nbsp;&nbsp; T sum = <span class="number">0</span>; <span class="number">14</span>. <span class="number">15</span>.&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">for</span> (<span class="keyword">uint</span> c = <span class="number">0</span>; c &lt; arg.Values.Size(); c++) <span class="number">16</span>.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sum += arg.Values[c]; <span class="number">17</span>. <span class="number">18</span>.&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">return</span> sum / arg.Values.Size(); <span class=

「用模板类算均值和中位数」

把价格序列塞进一个泛型容器,再顺手算出均值与中位数,是做分布体检的常见起手式。下面这段 MT5 代码定义了一个模板类,私有成员 Values[] 存原始数据,Set() 负责释放旧数组并整段拷贝新数据。 Average() 走最直白的累加除以样本数:用 for 循环把 Values 里每个元素叠到 sum,再除以 Values.Size()。Median() 则先拷贝一份到 Tmp[],ArraySort 排好序,偶数长度时取中间两数均值,奇数时直接取中点——位运算 (Tmp.Size() & 1) 为 0 即偶数。 OnStart 里给了两组实测样本:双精度数组 H 七个元素(2.05,1.97,1.87,1.75,1.99,2.01,1.83),字符数组 K 五个元素(12,4,7,23,38)。你在 MT5 里跑一遍,H 的均值约 1.924、中位数 1.97;K 的中位数 12。外汇与贵金属波动大,这类统计只描述已发生样本,不预示下一步方向,实盘用须自担高风险。 宏 PrintX(X) 把变量名和值一起打印,调试时不用手写字符串。复制进 EA 的 OnStart 就能直接验证上面数字,比肉眼读数组快得多。

MQL5 / C++
class="kw">private:
    T Values[];
class="kw">public:
    class="type">void Set(const T &arg[])
    {
        ArrayFree(Values);
        ArrayCopy(Values, arg);
    }
    class="type">class="kw">double Average(class="type">void)
    {
        class="type">class="kw">double sum = class="num">0;
        for(class="type">uint c = class="num">0; c < Values.Size(); c++)
            sum += Values[c];
        class="kw">return sum / Values.Size();
    }
    class="type">class="kw">double Median(class="type">void)
    {
        T   Tmp[];
        ArrayCopy(Tmp, Values);
        ArraySort(Tmp);
        if(!(Tmp.Size() & class="num">1))
        {
            class="type">int i = (class="type">int)MathFloor(Tmp.Size() / class="num">2);
            class="kw">return (Tmp[i] + Tmp[i - class="num">1]) / class="num">2.0;
        }
        class="kw">return (class="type">class="kw">double)Tmp[Tmp.Size() / class="num">2];
    }
};
class="macro">#define PrintX(X) Print(class="macro">#X, " => ", X)
class="type">void OnStart(class="type">void)
{
    const class="type">class="kw">double H[] = {class="num">2.05, class="num">1.97, class="num">1.87, class="num">1.75, class="num">1.99, class="num">2.01, class="num">1.83};
    const class="type">char K[] = {class="num">12, class="num">4, class="num">7, class="num">23, class="num">38};

◍ 模板结构体的均值与中位数实测

把统计逻辑塞进模板结构体,是用 MQL5 写指标或 EA 时的常见偷懒法:一次定义,double 和 char 数组都能直接复用。下面这段结构体 st_Data 用 typename T 泛化,私有成员 T Values[] 存数据,公开方法里 Set 负责拷贝入参数组,Sum 跑循环累加,Average 用 Sum 除以元素个数强转 double,Median 则拷贝后排序取中值。 调用侧很直白:Info_1 是 double 版本,Info_2 是 char 版本,各自 Set(H) 和 Set(K) 后打印 Average 与 Median。注意 Median 里 ArraySort 作用在临时数组 Tmp 上,不会污染原始 Values 的顺序,这对后续还要按原序做价格行为判断的场景很关键。 开 MT5 建个脚本把 H、K 换成你自己的样本数组(比如最近 20 根收盘价),跑完 PrintX 出来的两个值若偏离较大,说明样本分布偏态明显,均值参考性会下降。外汇与贵金属杠杆高,这类统计仅作辅助,实盘信号需结合结构位验证。

MQL5 / C++
st_Data <class="type">class="kw">double> Info_1;
st_Data <class="type">char> Info_2;

Info_1.Set(H);
PrintX(Info_1.Average());
PrintX(Info_1.Median());

Info_2.Set(K);
PrintX(Info_2.Average());
PrintX(Info_2.Median());
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">template <class="kw">typename T>
class="kw">struct st_Data
{
class=class="str">"cmt">//+----------------+
   class="kw">private:
class=class="str">"cmt">//+----------------+
      T Values[];
class=class="str">"cmt">//+----------------+
   class="kw">public:
class=class="str">"cmt">//+----------------+
      class="type">void Set(const T &arg[])
      {
         ArrayFree(Values);
         ArrayCopy(Values, arg);
      }
class=class="str">"cmt">//+----------------+
      T Sum(class="type">void)
      {
         T sum = class="num">0;

         for (class="type">uint c = class="num">0; c < NumberOfElements(); c++)
            sum += Values[c];

         class="kw">return sum;
      }
class=class="str">"cmt">//+----------------+
      class="type">uint NumberOfElements(class="type">void)
      {
         class="kw">return Values.Size();
      }
class=class="str">"cmt">//+----------------+
      class="type">class="kw">double Average(class="type">void)
      {
         class="kw">return ((class="type">class="kw">double)Sum() / NumberOfElements());
      }
class=class="str">"cmt">//+----------------+
      class="type">class="kw">double Median(class="type">void)
      {
         T  Tmp[];

         ArrayCopy(Tmp, Values);
         ArraySort(Tmp);

中位数与均值在模板类里的分叉处理

上面这段把偶数和奇数样本的中位数的求法拆开了:当容器内元素个数为偶数时,取中间两个值做平均;为奇数时直接取下标中间那个。逻辑写得很直白,但容易在边界下标上栽跟头,尤其 Tmp[i-1] 在空容器时会被触发越界。 看 OnStart 里的实测:H 数组有 7 个 double(2.05,1.97,1.87,1.75,1.99,2.01,1.83),排序后中位数应是 1.97;K 数组 5 个 char(12,4,7,23,38),中位数则是 12。把这两组直接丢进 MT5 脚本跑,PrintX 宏会把表达式和结果一起打印,方便你核对模板类是否算对。 外汇与贵金属行情序列用这类统计容器做快速中位数滤波时,高风险在于样本含跳空缺口,偶数切分可能把异常值夹进中间段,导致中位估计偏斜。建议先对 Tmp 做去极值再调 Median()。

MQL5 / C++
if (!(Tmp.Size() & class="num">1))
{
   class="type">int i = (class="type">int)MathFloor(Tmp.Size() / class="num">2);

   class="kw">return (Tmp[i] + Tmp[i - class="num">1]) / class="num">2.0;
}
class="kw">return (class="type">class="kw">double) Tmp[Tmp.Size() / class="num">2];
}
class=class="str">"cmt">//+----------------+
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define PrintX(X) Print(class="macro">#X, " => ", X)
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart(class="type">void)
{
   const class="type">class="kw">double H[] = {class="num">2.05, class="num">1.97, class="num">1.87, class="num">1.75, class="num">1.99, class="num">2.01, class="num">1.83};
   const class="type">char K[] = {class="num">12, class="num">4, class="num">7, class="num">23, class="num">38};

   st_Data <class="type">class="kw">double> Info_1;
   st_Data <class="type">char> Info_2;

   Info_1.Set(H);
   PrintX(Info_1.Average());
   PrintX(Info_1.Median());

   Info_2.Set(K);
   PrintX(Info_2.Average());
   PrintX(Info_2.Sum());
   PrintX(Info_2.NumberOfElements());
   PrintX(Info_2.Sum() / Info_2.NumberOfElements());
   PrintX(Info_2.Median());
}

「记住这一条就够了」

把结构体重载玩顺之前,先别急着碰 OOP 那套复杂封装。本文附录里给的练习代码,足够你在 MT5 里新建一个 .mq5 把结构体函数重载跑通——第一次看懵很正常,多编译几次就懂了。 下一篇会把这套写法扩到任意数据类型,而且不靠面向对象也能做出来。外汇和贵金属杠杆高、滑点狠,拿结构体重载写指标或辅助逻辑时,先在模拟盘验证再上实盘。 能徒手写清结构体里的公有和私有区分,你已经超过八成只会复制粘贴 EA 的散户了。

常见问题

定义一个struct包含double open和close字段,用数组存历史K线,遍历累加后除以总数即得均值,代码可直接复制到脚本里跑。
模板类对空值或奇数偶数样本的分叉处理不同,中位数取中间值、均值取算术平均,样本数低于3时差异最明显。
可以,小布盯盘的AIGC分析能直接读取品种页数据输出均值中位数并生成标注,打开对应页面即可查看,不用自己写代码。
可能更抗极端影线干扰,倾向更适合看真实价格重心,但样本太小仍会失真,建议结合结构位一起判断。
先对拷贝数组排序再用size/2取索引,务必判断数组长度大于0,偶数样本取中间两数平均避免越界。