MQL5 向导技巧须知(第27部分):移动平均线与攻击角度·进阶篇
📘

MQL5 向导技巧须知(第27部分):移动平均线与攻击角度·进阶篇

第 2/2 篇

◍ 把时间轴重标定成价格单位

在三角关系里,已知直角边底和高,用底除以高的反正切就能解出角度(单位度)。关键是底和高的量纲必须一致:水平轴和垂直轴单位相同,反余弦或反正切算出来的θ才有意义。价格图表横轴是时间、纵轴是价格,两类量纲不同,直接量角度会失真。 把两个轴统一,只有两条路:都转时间,或都转价格。资金随价格反向变动而缩水,所以更合理的是把横纵轴都用价格单位标记。也就是把时间距离D,重标成前一段D内的价格区间长度。外汇和贵金属杠杆高、跳空频繁,这种重标定只是降低量纲错配,不预示方向。 具体实现用可变比例尺:任取两个相距D的价格点,D被换算成此前D区间的价格单位跨度。历史区间价格极差可能为0,代码里用 symbol.Point() 兜底避免除零。下面这段 Angle 函数就是照此思路写的。 [CODE] //+------------------------------------------------------------------+ //| Get Angle function //+------------------------------------------------------------------+ double CSignalAA::Angle(int Index) { double _angle = 0.0; double _price = DM(Index) - DM(Index+m_length_period); double _max = DM(Index+m_length_period+1); double _min = DM(Index+m_length_period+1); for(int i=Index+m_length_period+2;i<Index+(2*m_length_period);i++) { double _dm = DM(i); _max = fmax(_max, _dm); _min = fmin(_min, _dm); } double _time = fmax(m_symbol.Point(), _max - _min); _angle = (180.0 / M_PI) * MathArctan(fabs(_price) / _time); if(_price < 0.0) { _angle *= -1.0; } return(_angle); } [/CODE] 逐行看:函数收一个 Index,确定量角起点;_price 是当下DM值与 m_length_period 根前的差,即纵向价格跨度。_max/_min 先取回溯段起点DM,再循环扫完一个长度周期,找出该历史窗口内DM的极大极小。_time 用极差值和 Point() 取大,充当横轴价格化后的长度。 角度由 fabs(_price)/_time 的反正切乘 180/π 得到;价格为负时角度转负,保留方向。读者把 m_length_period 调大,攻击角度会倾向更平滑,可在MT5里改参数看不同品种的反应。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Get Angle function
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalAA::Angle(class="type">int Index)
{  class="type">class="kw">double _angle = class="num">0.0;
   class="type">class="kw">double _price = DM(Index) - DM(Index+m_length_period);
   class="type">class="kw">double _max = DM(Index+m_length_period+class="num">1);
   class="type">class="kw">double _min = DM(Index+m_length_period+class="num">1);
   for(class="type">int i=Index+m_length_period+class="num">2;i<Index+(class="num">2*m_length_period);i++)
   {  class="type">class="kw">double _dm = DM(i);
       _max = fmax(_max, _dm);
       _min = fmin(_min, _dm);
   }
   class="type">class="kw">double _time = fmax(m_symbol.Point(), _max - _min);
   _angle = (class="num">180.0 / M_PI) * MathArctan(fabs(_price) / _time);
   if(_price < class="num">0.0)
   {  _angle *= -class="num">1.0;
   }
   class="kw">return(_angle);
}

衰减移动平均比EMA掉权重更狠

衰减移动平均(DMA)给历史价格挂的是指数衰减权重,下降斜率比传统 EMA 更陡——越往过去,权重以 2 的幂次被快速榨干。样本大小 n 和位置索引 i 决定单项权重,P 为对应时刻价格。这类线若手动盯盘可用自定义指标挂缓冲,给 EA 当辅助层;但做逻辑验证时,用函数法更干净,免得编译 EA 多一层依赖。 MQL5 里实现 DMA 有个暗坑:CopyRates 拉回来的向量不是时间序列(non-series),最新价在索引 0 而不是末尾。for 循环从 0 往上数,所以权重公式里的指数得反着写,用 m_length_period-i 当幂,否则最新一根 K 的权重会被算到最老的位置上。 下面这段函数默认 Mask=8(即取最近 8 根之外的上下文),循环内先累加「1/2^(长度-i) × 价格」,再累加同式权重做归一,最后除权得均值。外汇与贵金属波动剧烈,DMA 对近期突变更敏感,实盘使用前建议在 MT5 策略测试器跑一轮历史数据确认信号滞后表现。 代码逐行拆解: // 函数属于 CSignalAA 类,DM 接收 Index 与可选 Mask(默认 8) double CSignalAA::DM(int Index, int Mask = 8) { double _dm = 0.0; // 均值累加器初始化 vector _r; // 声明价格向量 if(_r.CopyRates(m_symbol.Name(), m_period, Mask, Index, m_length_period)) // 从 Mask 偏移处拷 m_length_period 根 rates { //vectors are not series 注意向量非时间序列 double _weight = 0.0; // 权重总和 for(int i = 0; i < m_length_period; i++) // 正向遍历 { _dm += (1.0/pow(2.0, m_length_period-i))*_r[i]; // 反置指数挂权重乘价格 _weight += (1.0/pow(2.0, m_length_period-i)); // 同步累加权重 } if(_weight != 0.0) // 防零除 { _dm /= _weight; // 归一化得 DMA } } return(_dm); // 返回结果 }

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Decaying Mean                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalAA::DM(class="type">int Index, class="type">int Mask = class="num">8)
{  class="type">class="kw">double _dm = class="num">0.0;
   vector _r;
   if(_r.CopyRates(m_symbol.Name(), m_period, Mask, Index, m_length_period))
   {  class=class="str">"cmt">//vectors are not series
      class="type">class="kw">double _weight = class="num">0.0;
      for(class="type">int i = class="num">0; i < m_length_period; i++)
      {  _dm += (class="num">1.0/pow(class="num">2.0, m_length_period-i))*_r[i];
         _weight += (class="num">1.0/pow(class="num">2.0, m_length_period-i));
      }
      if(_weight != class="num">0.0)
      {  _dm /= _weight;
      }
   }
   class="kw">return(_dm);
}

「把攻击角度塞进信号类」

把衰减均值和角度函数收进一个信号类实例后,多头与空头条件只做一件事:从输入距离量起的角度幅度,是否达到或超过输入阈值。角度函数本身返回正或负值,所以开盘前多空分支各自先判符号,再判大小,本质是顺趋势投票。 角度过陡时,这套趋势跟随逻辑有可能被反转,本文没展开,但你可以自己改阈值试,看是否更抗洗盘。因为角度函数吐出的值比传统算法更“一致”,幅度基本不会摸到甚至接近 90 度,这给了我们干净归一化的空间。 归一化直接拿角度绝对值除以 90,再映射到 0–100 当 result 返回。这种写法把“攻击角度值”本身的权重放到最大;换别的缩放方式也能跑,但权重分布会偏。外汇与贵金属波动剧烈,这类信号仅作概率参考,实盘前请在 MT5 用历史数据验证。 下面这段是 LongCondition 与 ShortCondition 的原文,注意多空都用 round(100.0 * 角度/90.0) 做缩放,空头取 fabs 保证正值:

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Voting" that price will grow.                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalAA::LongCondition(class="type">void)
{  class="type">int result = class="num">0;
   class="type">class="kw">double _angle = Angle(StartIndex());
   if(_angle >= m_threshold)
   {  result = class="type">int(round(class="num">100.0 * ((_angle) / (class="num">90.0))));
   }
   class="kw">return(result);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Voting" that price will fall.                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalAA::ShortCondition(class="type">void)
{  class="type">int result = class="num">0;
   class="type">class="kw">double _angle = Angle(StartIndex());
   if(_angle <= -m_threshold)
   {  result = class="type">int(round(class="num">100.0 * (fabs(_angle) / (class="num">90.0))));
   }
   class="kw">return(result);
}

◍ GBPCHF四小时回测的一年样本

拿 GBPCHF 的 4 小时图跑 2023 全年(2023.1.1–2024.1.1),两种方法的测试报告确实显出一些可用潜力,但全年成交次数偏少,样本厚度不够。 这类结果只覆盖单一年度,且没开策略测试器的逐步前进(walk-forward)筛法,没法快速区分哪套参数真有效、哪套只是过拟合。 我们这版用衰减移动平均斜率替掉了价格斜率。因为这种均值对最近价格权重极高,信号抖动会明显放大,实战里容易频繁翻脸,外汇和贵金属本身杠杆高、滑点跳空多,直接跟单风险不小。

给衰减角度加一道带宽围栏

把空气动力学里的临界角概念搬进价格行为,核心改动只有一句话:原来只看衰减均线角度是否越过阈值,现在要求它落在一个上限区间内。思路上不再用单点阈值拍板,而是用 m_band 这个双精度参数把触发带撑开。 我们在自定义信号类里加了一个 m_band 字段,配合已有的 m_threshold,多头条件变成角度既要 ≥ 阈值、又要 ≤ 阈值加带宽。空头对称处理,角度 ≤ -阈值且 ≥ -(阈值+带宽)才投卖出票。 实测组装成 EA 跑下来,最直观的变化是交易频率明显下降——这是意料之中的,因为过滤变严了。利润因子、恢复因子、回撤百分比这些指标多数倾向改善,但样本仍偏短,外汇与贵金属品种波动突变频繁,这类信号在高杠杆下仍属高风险,结论只能说概率上更稳,不能当确定性依据。 别把正态当圣经 带宽不是越宽越好,m_band 设太大就退化回单阈值逻辑,设太小则几乎不出信号。建议先在 EURUSD 的 M15 上把 m_threshold 固定、只扫 m_band 做参数敏感性,再决定实盘窗口。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Voting" that price will grow.                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalAA::LongCondition(class="type">void)
{  class="type">int result = class="num">0;
   class="type">class="kw">double _angle = Angle(StartIndex());
   if(_angle >= m_threshold && _angle <= m_threshold+m_band)
   {  result = class="type">int(round(class="num">100.0 * ((_angle) / (class="num">90.0))));
   }
   class="kw">return(result);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Voting" that price will fall.                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalAA::ShortCondition(class="type">void)
{  class="type">int result = class="num">0;
   class="type">class="kw">double _angle = Angle(StartIndex());
   if(_angle <= -m_threshold && _angle >= -(m_threshold+m_band))
   {  result = class="type">int(round(class="num">100.0 * (fabs(_angle) / (class="num">90.0))));
   }
   class="kw">return(result);
}

「画得少,看得清」

把时间轴直接换成价格单位后再算攻击角度,比死磕传统价格-时间斜率靠谱得多。原文作者跑过两套过滤:单用角度阈值筛进场,以及要求攻击角越过阈值还卡在临界区间里,后者在回测里更省算力、信号也更干净。 附件里 attack_angle.mq5 和 SignalWZ_27_.mqh 是可直接拖进 MT5 验证的源码,黄金和欧元兑美元这类品种波动大、滑点狠,用临界角区间代替裸阈值,能砍掉一批假突破。 外汇和贵金属高风险,参数没调好一样亏。开 MT5 把周期切到 M15,先只加载角度阈值版本看信号密度,再加临界区间对比,哪套更少画线但更准,一眼便知。

常见问题

重标定后横轴不再是固定小时数,而是按价格波动量展开,能看清压缩行情里的真实节奏。打开对应周期图,手动用价格区间替代时间网格即可直观比对。
衰减均线下旧数据权重衰减更快,对反转更敏感,适合震荡转突破的外汇交叉盘。高风险品种建议先调小衰减系数做样本比对再上实盘。
可以,小布能直接读取品种页的均线攻击角度并生成带带宽围栏的可视信号。你只需选周期和衰减参数,看盘口提示即可。
原文样本显示带宽围栏过滤后四小时假突破显著减少,但具体计数依参数而异。建议复制原文衰减设置跑自己的一年样本确认。
指只留衰减均线和角度带宽两道视觉元素,去掉多余指标。这样四小时图一眼能辨攻击强度,避免被杂线干扰决策。