从基础到中级:结构(五)·进阶篇
◍ 用结构体和自定义函数算均值与中位数
在 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 判断奇偶,偶数时取中间两数均值,奇数时直接取下标中间值。这种写法比调用内置函数更透明,也方便你接自己的权重逻辑。外汇和贵金属波动大,这类统计只描述已发生样本,不预示后续方向,实盘需自担高风险。
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> <<span class="keyword">typename</span> T> <span class="number">05</span>. <span class="keyword">struct</span> st_Data <span class="number">06</span>. { <span class="number">07</span>. <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> <<span class="keyword">typename</span> T> <span class="number">11</span>. <span class="keyword">double</span> Average(<span class="keyword">const</span> st_Data <T> &arg) <span class="number">12</span>. { <span class="number">13</span>. T sum = <span class="number">0</span>; <span class="number">14</span>. <span class="number">15</span>. <span class="keyword">for</span> (<span class="keyword">uint</span> c = <span class="number">0</span>; c < arg.Values.Size(); c++) <span class="number">16</span>. sum += arg.Values[c]; <span class="number">17</span>. <span class="number">18</span>. <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 就能直接验证上面数字,比肉眼读数组快得多。
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 出来的两个值若偏离较大,说明样本分布偏态明显,均值参考性会下降。外汇与贵金属杠杆高,这类统计仅作辅助,实盘信号需结合结构位验证。
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()。
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 的散户了。