均线类型洞察力测试:从 SMA 基线看清不同均线的真实表现(基础篇)
📊

均线类型洞察力测试:从 SMA 基线看清不同均线的真实表现(基础篇)

(1/3)· 很多交易者凭习惯选均线类型,却从没把 SMA、AMA、DEMA、TEMA、FrAMA 拉到同一回测台上比过差异

含代码示例偏理论 第 1/3 篇

不少人在 EA 里随手挂一条均线就开跑,至于它和别的均线类型到底差在哪、哪种更适合自己的周期,从没系统比过。本文是均线类型洞察力测试系列的第一部分,先把 SMA 基线跑出来,再给后面几种非常规均线留好对照坐标。

动手比选均线类型才有发言权

很多交易者默认就用 SMA,却没在 MT5 里真正拉过其他均线做对比。MQL5 官方社区一篇 2024 年 2 月的测试帖被浏览 1434 次,说明这类基础验证仍有大量人没做过。 打开 MT5 的「导航器—指标—趋势」随便拖一个均线到图表,在参数里把 MA_Method 从 MODE_SMA 改成 MODE_EMA 或 MODE_LWMA,同一周期下拐点响应速度差异肉眼可见。外汇与贵金属杠杆高,均线只是延迟描述工具,任何交叉信号都只是概率倾向,不是方向保证。 真正有用的洞察来自你自己跑一遍:用策略测试器加载不同均线方法回测同一段 EURUSD H1 数据,记录最大回撤与交易次数,而不是看别人结论。

「从 SMA 出发横向跑一遍均线家族」

前一篇我们拆过 SMA、WMA、EMA 这几种最常见均线的设计差异,这一节换思路:只用简单移动平均做基准,把其它几类自适应或高阶指数均线拉进 MT5 策略测试器里同台回测,看谁更扛打。 我们会逐一覆盖 SMA 系统、iAMA、iDEMA、iTEMA、iFrAMA,最后把结果叠回 SMA 基准线做对比。过程中我会把每个指标的参数往最优方向调,但那仅限教学演示——你自己的风格未必吃这一套。 为了能直接复现,我会写一个轻量 EA 丢进内置 Strategy Tester 跑历史。没看过测试器读数方法的,建议先补一下报告里该盯哪些字段,不然回测出来也是瞎看。 外汇与贵金属杠杆高、滑点跳空频繁,任何均线系统回测飘红都不代表实盘概率等同;真上真实账户前,必须自己重跑优化并自担风险。

◍ 用一根50期SMA做EURUSD交叉回测

只靠价格与简单移动平均线的交叉就能自动下单:当收盘价上穿SMA生成买信号,下穿则生成卖信号,逻辑不需要任何过滤器。下面这套EA把信号写成OnTick里的条件判断,有新柱才检查一次,避免同根K线反复开仓。 全局引入Trade.mqh后,开放三个输入参数:lotSize默认1手、timeFrame默认H1、MAPeriod默认50。OnInit里用iMA拿到SMA句柄(MODE_SMA+PRICE_CLOSE),用iBars记总柱数;OnTick复制最近3根MqlRates和指标缓冲,取前一根与再前一根的收盘及SMA值做比较。 回测跑在EURUSD、2022年1月1日至6月30日、H1周期、SMA(50)、1手固定仓位。净利润2700.30,余额相对回撤37.07%,净值相对回撤41.76%,利润因子1.10,期望收益12.68,夏普0.57。外汇与贵金属属高风险品种,这类裸均线系统在趋势段可能盈利,震荡段回撤偏大,实盘前务必自测。 想验证就直接把代码贴进MT5策略测试器,改MAPeriod到20或100,看利润因子和回撤怎么变——参数敏感度和品种适配才是下一步要盯的。

MQL5 / C++
class="macro">#include <Trade\Trade.mqh>
input class="type">class="kw">double lotSize = class="num">1;
input ENUM_TIMEFRAMES timeFrame = PERIOD_H1;
input class="type">int MAPeriod= class="num">50;
class="type">int simpleMA;
class="type">int barsTotal;
CTrade trade;
simpleMA = iMA(_Symbol, timeFrame, MAPeriod, class="num">0, MODE_SMA, PRICE_CLOSE);
  barsTotal=iBars(_Symbol,timeFrame);
  class="type">MqlRates priceArray[];
  class="type">class="kw">double mySMAArray[];
  ArraySetAsSeries(priceArray,true);
  ArraySetAsSeries(mySMAArray,true);
  class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
  class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
class="type">int Data=CopyRates(_Symbol,_Period,class="num">0,class="num">3,priceArray);
CopyBuffer(simpleMA,class="num">0,class="num">0,class="num">3,mySMAArray);
  class="type">class="kw">double lastClose=(priceArray[class="num">1].close);
  class="type">class="kw">double SMAVal = NormalizeDouble(mySMAArray[class="num">1],_Digits);
  class="type">class="kw">double prevClose=(priceArray[class="num">2].close);
  class="type">class="kw">double prevSMAVal = NormalizeDouble(mySMAArray[class="num">2],_Digits);
class="type">int bars=iBars(_Symbol,timeFrame);
if(barsTotal != bars)
barsTotal=bars;
      if(prevClose<prevSMAVal && lastClose>SMAVal)
        {
         trade.PositionClose(_Symbol);
         trade.Buy(lotSize,_Symbol,Ask,class="num">0,class="num">0,NULL);
        }
      if(prevClose>prevSMAVal && lastClose<SMAVal)
        {
         trade.PositionClose(_Symbol);

用均线穿越触发双向头寸切换

这段 EA 骨架把 SMA 穿越当成唯一信号源:当上一根收盘价在 50 周期 SMA 下方、当前根收上方,视为多头倾向,先平再买;反向则先平再卖。参数里 lotSize=1、timeFrame=PERIOD_H1、MAPeriod=50,意味着在 H1 图上以 50 根收盘价的简单平均作门槛。 OnInit 里用 iMA(_Symbol, timeFrame, MAPeriod, 0, MODE_SMA, PRICE_CLOSE) 拿到均线句柄,barsTotal 记录初始 bar 数;OnTick 只在 barsTotal 变化(即新 K 线成型)时才判断,避免同根内反复发单。 CopyRates 取最近 3 根、CopyBuffer 取均线最近 3 值,比较 priceArray[2] 与 mySMAArray[2]、priceArray[1] 与 mySMAArray[1] 的上下关系。注意这里用的是索引 1 和 2,即已完成的前两根,不是当前未定型的 0 号 bar。 外汇与贵金属杠杆高,均线穿越在震荡市会连续假突破,实盘前务必在 MT5 策略测试器用历史数据跑一遍,观察 H1 周期下 50 SMA 在你常做品种上的胜率分布。

MQL5 / C++
trade.Sell(lotSize,_Symbol,Bid,class="num">0,class="num">0,NULL);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                     SMA_System.mq5 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Trade\Trade.mqh>
input class="type">class="kw">double lotSize = class="num">1;
input ENUM_TIMEFRAMES timeFrame = PERIOD_H1;
input class="type">int MAPeriod= class="num">50;
class="type">int simpleMA;
class="type">int barsTotal;
CTrade trade;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   simpleMA = iMA(_Symbol, timeFrame, MAPeriod, class="num">0, MODE_SMA, PRICE_CLOSE);
   barsTotal=iBars(_Symbol,timeFrame);
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   class="type">MqlRates priceArray[];
   class="type">class="kw">double mySMAArray[];
   class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   ArraySetAsSeries(priceArray,true);
   ArraySetAsSeries(mySMAArray,true);
   class="type">int Data=CopyRates(_Symbol,_Period,class="num">0,class="num">3,priceArray);
   CopyBuffer(simpleMA,class="num">0,class="num">0,class="num">3,mySMAArray);
   class="type">class="kw">double lastClose=(priceArray[class="num">1].close);
   class="type">class="kw">double SMAVal = NormalizeDouble(mySMAArray[class="num">1],_Digits);
   class="type">class="kw">double prevClose=(priceArray[class="num">2].close);
   class="type">class="kw">double prevSMAVal = NormalizeDouble(mySMAArray[class="num">2],_Digits);
   class="type">int bars=iBars(_Symbol,timeFrame);
   if(barsTotal != bars)
     {
      barsTotal=bars;
      if(prevClose<prevSMAVal && lastClose>SMAVal)
        {
         trade.PositionClose(_Symbol);
         trade.Buy(lotSize,_Symbol,Ask,class="num">0,class="num">0,NULL);
        }
      if(prevClose>prevSMAVal && lastClose<SMAVal)
        {
         trade.PositionClose(_Symbol);

「把卖单真正挂进市场」

前面几节把信号和仓位都算清楚了,最后一步就是调用交易对象的 Sell 方法把市价卖单发出去。这里用的是 CTrade 类的实例 trade,参数顺序很固定:手数、品种、挂单价、止损、止盈、异步回调标识。 代码里给的挂单价直接取 Bid,也就是当前买价,说明这是一笔立刻成交的市价单而不是挂单。止损和止盈都填 0,意味着这一笔不下 protective orders,风险完全裸奔——外汇和贵金属杠杆高,这种写法只适合你在别处已经用虚拟挂单或人工盯盘兜底的场景。 末尾的 NULL 是异步执行标记位,传 NULL 表示走同步等待成交回报;如果你在高频率策略里想不阻塞主线程,可以换成对应的回调函数名。改完参数后直接贴进 MT5 的 EA 里编译,就能看到卖单在真实环境里跑出来。

MQL5 / C++
      trade.Sell(lotSize,_Symbol,Bid,class="num">0,class="num">0,NULL);
   }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
把回测对照交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到不同均线类型的叠加与信号分布,你只需专注判断哪类更适合自己的风格。

常见问题

SMA 是最朴素的基线,价格与其交叉的逻辑直观,拿它做参照能更清楚看出 AMA、DEMA 等在延迟和噪声上的偏移。
iAMA 靠效率比调节平滑系数,iFrAMA 用分形维度估市场粗糙度,两者对趋势突变的反应节奏不完全一致,需回测对比。
DEMA 通过抵消部分滞后项来逼近价格,理论延迟低于同周期 EMA,但实际表现仍取决于参数与品种波动结构。
可以,小布盯盘的品种页支持多均线叠加与信号标记,省去自己写回测框架的初始工作量,但参数优化仍要你自己做。
不能。文中设置仅作教学演示,外汇贵金属杠杆高、风险大,任何策略都需自行扩样测试并衡量实际账户下的效果概率。