测试不同的移动平均类型以了解它们的洞察力·进阶篇
📘

测试不同的移动平均类型以了解它们的洞察力·进阶篇

第 2/3 篇

「用 AMA 交叉做 H1 趋势跟随的实盘回测底稿」

Kaufman 的 AMA(也叫 KAMA)核心是用效率比率动态调平滑常数,波动噪声大时放慢、趋势清晰时跟紧,属于典型的趋势跟随工具,适合抓转折而非震荡刷单。 策略信号只认收盘价与 AMA 的穿越:当根收盘站上且前一根收在下方,偏多;当根收盘跌破且前一根收在上方,偏空。逻辑简单,但外汇与贵金属属高风险品种,信号失效时回撤可能快速扩大。 下面是 MT5 可直接编译的 EA 骨架,关键在 iAMA 句柄的参数排布:_Symbol 取当前品种,timeFrame 默认 PERIOD_H1,AMA 周期 50、快线 5、慢线 100、偏移 0、用收盘价。 [CODE] //+------------------------------------------------------------------+

//
//iAMA_System.mq5

//+------------------------------------------------------------------+ #include <Trade\Trade.mqh> input double lotSize = 1; input ENUM_TIMEFRAMES timeFrame = PERIOD_H1; input int MAPeriod= 50; input int fastMAPeriod= 5; input int slowMAPeriod= 100; int adaptiveMA; int barsTotal; CTrade trade; //+------------------------------------------------------------------+

//Expert initialization function

//+------------------------------------------------------------------+ int OnInit() { adaptiveMA = iAMA(_Symbol, timeFrame, MAPeriod,fastMAPeriod,slowMAPeriod, 0, PRICE_CLOSE); barsTotal=iBars(_Symbol,timeFrame); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+

//Expert tick function

//+------------------------------------------------------------------+ void OnTick() { MqlRates priceArray[]; double myAMAArray[]; double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); ArraySetAsSeries(priceArray,true); ArraySetAsSeries(myAMAArray,true); int Data=CopyRates(_Symbol,timeFrame,0,3,priceArray); CopyBuffer(adaptiveMA,0,0,3,myAMAArray); double lastClose=(priceArray[1].close); double AMAVal = NormalizeDouble(myAMAArray[1],_Digits); double prevClose=(priceArray[2].close); double prevAMAVal = NormalizeDouble(myAMAArray[2],_Digits); int bars=iBars(_Symbol,timeFrame); if(barsTotal != bars) { barsTotal=bars; 代码逐行拆解:

  • #include <Trade\Trade.mqh> 引入交易类,下单靠它。
  • input 段把手数、周期、快慢线参数暴露给 EA 面板,默认 H1+AMA50/快5/慢100。
  • OnInitiAMA(...) 拿指标句柄,偏移 0、价型 PRICE_CLOSE;iBars 记初始柱数。
  • OnTickArraySetAsSeries(...,true) 让数组下标 0 为最新;CopyRates 取最近 3 根 K 线,CopyBuffer 取 AMA 值。
  • priceArray[1] 是前一根收盘、[2] 是前两根,对应 AMA 同下标,用来判穿越。
  • 末尾 if(barsTotal != bars) 仅在出新柱时刷新计数,避免每 tick 重复触发。

同参数回测(手数 1、H1)跑出的硬数字:净利润 3638.20(36.39%),余额回撤 22.48%、净值回撤 35.53%,利润因子 1.31,期望收益 35.67,恢复因子 0.65,夏普 0.86。这类结果只说明历史样本里该交叉系统倾向微利,换品种或时段可能明显走形,开 MT5 自己跑一遍最实在。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//|                 iAMA_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;
input class="type">int fastMAPeriod= class="num">5;
input class="type">int slowMAPeriod= class="num">100;
class="type">int adaptiveMA;
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()
  {
   adaptiveMA = iAMA(_Symbol, timeFrame, MAPeriod,fastMAPeriod,slowMAPeriod, class="num">0, 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 myAMAArray[];
   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(myAMAArray,true);
   class="type">int Data=CopyRates(_Symbol,timeFrame,class="num">0,class="num">3,priceArray);
   CopyBuffer(adaptiveMA,class="num">0,class="num">0,class="num">3,myAMAArray);
   class="type">class="kw">double lastClose=(priceArray[class="num">1].close);
   class="type">class="kw">double AMAVal = NormalizeDouble(myAMAArray[class="num">1],_Digits);
   class="type">class="kw">double prevClose=(priceArray[class="num">2].close);
   class="type">class="kw">double prevAMAVal = NormalizeDouble(myAMAArray[class="num">2],_Digits);
   class="type">int bars=iBars(_Symbol,timeFrame);
   if(barsTotal != bars)
     {
      barsTotal=bars;

AMA 穿越后的反手开仓逻辑

这段代码展示的是在 AMA(自适应移动平均)发生方向穿越时,先把当前持仓平掉、再反手建立新头寸的实战写法。它的判定核心是:当最新收盘价站上 AMA 值、且前一根收盘价还在 AMA 下方,视为多头穿越;反向则视为空头穿越。 具体执行上,多头穿越分支先调用 PositionClose(_Symbol) 平掉同品种现有仓位,紧接着以 Ask 价买入 lotSize 手,止损止盈参数传 0 表示不预设,注释串 NULL 留作订单备注。空头分支对称处理,以 Bid 价卖出。 在 MT5 里直接套用这段逻辑时,AMAVal 与 prevAMAVal 需要你自己在 OnTick 前面算好(例如用 iAMA 句柄取缓冲值),否则编译会通过但运行时永远不触发。外汇与贵金属杠杆高,这类反转信号在震荡市可能连续扫损,建议先用策略测试器跑 2023 年 XAUUSD 的 H1 数据看信号密度再上实盘。

MQL5 / C++
if(lastClose>AMAVal && prevClose<prevAMAVal)
  {
   trade.PositionClose(_Symbol);
   trade.Buy(lotSize,_Symbol,Ask,class="num">0,class="num">0,NULL);
  }
 if(lastClose<AMAVal && prevClose>prevAMAVal)
  {
   trade.PositionClose(_Symbol);
   trade.Sell(lotSize,_Symbol,Bid,class="num">0,class="num">0,NULL);
  }

◍ 用 DEMA 交叉做裸 K 趋势跟踪的回测真相

双重指数移动平均(DEMA)由 Patrick Mulloy 提出,核心思路是用 (2×EMA₁ − EMA₂) 抵消单层 EMA 的滞后,让均线对价格转折更敏感。它本质还是趋势跟随工具,靠价格与 DEMA 的位置切换来识别方向,不是预测器。 交易逻辑只认收盘价穿越:当收价站上 DEMA 且前一根收价还在 DEMA 下方,倾向开多;收价跌破 DEMA 且前一根在上方,倾向开空。比单纯均线交叉少一点钝化,但震荡市照样吃耳光。 下面这段 MT5 代码用 iDEMA 取句柄,默认 _Symbol、H1、周期 50、偏移 0、收盘价计算。OnTick 里只抓最近 3 根 K 线,用 priceArray[1] 对比 myDEMAArray[1]、priceArray[2] 对比 myDEMAArray[2] 判定交叉,新柱才触发一次,避免重复下单。 //+------------------------------------------------------------------+

//iDEMA_System.mq5

//+------------------------------------------------------------------+ #include <Trade\Trade.mqh> // 引入交易类,封装下单方法 input double lotSize = 1; // 外部手数参数,默认 1 手 input ENUM_TIMEFRAMES timeFrame = PERIOD_H1; // 外部周期参数,默认 1 小时 input int MAPeriod= 50; // 外部 MA 周期,默认 50 int DEMA; // 存 iDEMA 指标句柄 int barsTotal; // 记录上一帧 K 线总数,用于新柱判断 CTrade trade; // 交易对象实例 //+------------------------------------------------------------------+

//Expert initialization function

//+------------------------------------------------------------------+ int OnInit() { DEMA = iDEMA(_Symbol, timeFrame, MAPeriod,0, PRICE_CLOSE); // 创建 DEMA 句柄:当前品种、H1、50期、无偏移、收价 barsTotal=iBars(_Symbol,timeFrame); // 初始化时已存在 K 线数 return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+

//Expert tick function

//+------------------------------------------------------------------+ void OnTick() { MqlRates priceArray[]; // 存放 K 线结构(开高低收时间量) double myDEMAArray[]; // 存放 DEMA 数值缓冲 double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); // 标准化卖价精度 double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); // 标准化买价精度 ArraySetAsSeries(priceArray,true); // 数组倒序,索引0为最新 ArraySetAsSeries(myDEMAArray,true); // 同上,DEMA 缓冲倒序 int Data=CopyRates(_Symbol,timeFrame,0,3,priceArray); // 拷贝最近 3 根 K 线 CopyBuffer(DEMA,0,0,3,myDEMAArray); // 拷贝 DEMA 最近 3 个值到缓冲 double lastClose=(priceArray[1].close); // 上一根收盘价(索引1,非即时柱) double DEMAVal = NormalizeDouble(myDEMAArray[1],_Digits); // 对应 DEMA 值 double prevClose=(priceArray[2].close); // 前一根收盘价 double prevDEMAVal = NormalizeDouble(myDEMAArray[2],_Digits); // 前一根 DEMA 值 int bars=iBars(_Symbol,timeFrame); // 当前 K 线总数 if(barsTotal != bars) // 若有新柱 { barsTotal=bars; // 更新计数 if(lastClose>DEMAVal && prevClose<prevDEMAVal) // 金叉条件:上根收破上、前根收在下 { 同一套参数(1 手、H1、50 期)跑下来,净利润 -961.60 美元(权益 -9.62%),余额回撤 39.62%、净值回撤 41.15%,利润因子 0.97,期望收益 -3.12,夏普 -0.21。外汇与贵金属属高风险品种,这类裸 DEMA 交叉在样本内已亏,实盘前务必自测多品种多周期。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                iDEMA_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 DEMA;
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()
  {
   DEMA = iDEMA(_Symbol, timeFrame, MAPeriod,class="num">0, 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 myDEMAArray[];
   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(myDEMAArray,true);
   class="type">int Data=CopyRates(_Symbol,timeFrame,class="num">0,class="num">3,priceArray);
   CopyBuffer(DEMA,class="num">0,class="num">0,class="num">3,myDEMAArray);
   class="type">class="kw">double lastClose=(priceArray[class="num">1].close);
   class="type">class="kw">double DEMAVal = NormalizeDouble(myDEMAArray[class="num">1],_Digits);
   class="type">class="kw">double prevClose=(priceArray[class="num">2].close);
   class="type">class="kw">double prevDEMAVal = NormalizeDouble(myDEMAArray[class="num">2],_Digits);
   class="type">int bars=iBars(_Symbol,timeFrame);
   if(barsTotal != bars)
     {
      barsTotal=bars;
      if(lastClose>DEMAVal && prevClose<prevDEMAVal)
        {

「DEMA 交叉反转的平仓反手写法」

当快线 DEMA 被收盘价从上方击穿,且上一根 K 线还在线上方时,系统先平掉当前持仓再反向买入。这段逻辑依赖 lastClose<DEMAVal 与 prevClose>prevDEMAVal 的双重确认,避免单根毛刺触发反复开仓。 外汇与贵金属杠杆高,这种反手策略在震荡市里可能连续止损,实盘前务必用 MT5 策略测试器跑至少 3 个月 tick 数据。 trade.PositionClose(_Symbol) 必须放在 trade.Buy 之前,否则同品种持仓未平会触发开仓失败并返回 ERR_INVALID_VOLUME 类错误。Sell 分支同理,用 Bid 价挂单、止盈止损先传 0 代表不设置。

MQL5 / C++
      trade.PositionClose(_Symbol);
      trade.Buy(lotSize,_Symbol,Ask,class="num">0,class="num">0,NULL);
      }
      if(lastClose<DEMAVal && prevClose>prevDEMAVal)
      {
      trade.PositionClose(_Symbol);
      trade.Sell(lotSize,_Symbol,Bid,class="num">0,class="num">0,NULL);
      }

TEMA 回测实况与句柄调用

三重指数移动平均(TEMA)由 Patrick Mulloy 提出,思路是在 EMA 基础上做三次平滑再线性组合:3×EMA1 − 3×EMA2 + EMA3,借此把曲线贴紧价格,对短期拐点的反应比 DEMA 更敏锐。MT5 里不用手算,直接调 iTEMA 拿句柄就能喂给 EA。 下面这段 EA 把 TEMA 周期固定为 50、小时图、1 标准手,逻辑仅是收盘价上穿 TEMA 就平掉反向仓。注意 OnInit 里 iTEMA 的第五个参数是 PRICE_CLOSE,改它就等于换价格源。 [CODE] //+------------------------------------------------------------------+

//iTEMA_System.mq5

//+------------------------------------------------------------------+ #include <Trade\Trade.mqh> input double lotSize = 1; input ENUM_TIMEFRAMES timeFrame = PERIOD_H1; input int MAPeriod= 50; int TEMA; int barsTotal; CTrade trade; //+------------------------------------------------------------------+

//Expert initialization function

//+------------------------------------------------------------------+ int OnInit() { TEMA = iTEMA(_Symbol, timeFrame, MAPeriod,0, PRICE_CLOSE); barsTotal=iBars(_Symbol,timeFrame); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+

//Expert tick function

//+------------------------------------------------------------------+ void OnTick() { MqlRates priceArray[]; double myTEMAArray[]; double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); ArraySetAsSeries(priceArray,true); ArraySetAsSeries(myTEMAArray,true); int Data=CopyRates(_Symbol,timeFrame,0,3,priceArray); CopyBuffer(TEMA,0,0,3,myTEMAArray); double lastClose=(priceArray[1].close); double TEMAVal = NormalizeDouble(myTEMAArray[1],_Digits); double prevClose=(priceArray[2].close); double prevTEMAVal = NormalizeDouble(myTEMAArray[2],_Digits); int bars=iBars(_Symbol,timeFrame); if(barsTotal != bars) { barsTotal=bars; if(lastClose>TEMAVal && prevClose<prevTEMAVal) { trade.PositionClose(_Symbol); [/CODE] 逐行拆解关键处: TEMA = iTEMA(_Symbol, timeFrame, MAPeriod,0, PRICE_CLOSE); —— 向当前品种请求 TEMA 句柄,周期来自外部输入 MAPeriod。 ArraySetAsSeries(myTEMAArray,true); —— 把缓冲区倒序,使 [1] 代表上一根 K 的 TEMA 值。 if(lastClose>TEMAVal && prevClose<prevTEMAVal) —— 判定价格从下方穿越 TEMA,触发平仓分支。 同一组参数跑完回测,净值曲线并不好看:净利润 −3973.10(−39.74%),余额相对回撤 63.98%,净值相对回撤 66.06%,利润因子仅 0.90,夏普比率 −0.83。外汇与贵金属属高杠杆品种,这类纯穿越策略在震荡市大概率持续失血,建议把周期调到 20 以下或叠加强弱过滤再验。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                iTEMA_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 TEMA;
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()
  {
   TEMA = iTEMA(_Symbol, timeFrame, MAPeriod,class="num">0, 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 myTEMAArray[];
   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(myTEMAArray,true);
   class="type">int Data=CopyRates(_Symbol,timeFrame,class="num">0,class="num">3,priceArray);
   CopyBuffer(TEMA,class="num">0,class="num">0,class="num">3,myTEMAArray);
   class="type">class="kw">double lastClose=(priceArray[class="num">1].close);
   class="type">class="kw">double TEMAVal = NormalizeDouble(myTEMAArray[class="num">1],_Digits);
   class="type">class="kw">double prevClose=(priceArray[class="num">2].close);
   class="type">class="kw">double prevTEMAVal = NormalizeDouble(myTEMAArray[class="num">2],_Digits);
   class="type">int bars=iBars(_Symbol,timeFrame);
   if(barsTotal != bars)
     {
      barsTotal=bars;
      if(lastClose>TEMAVal && prevClose<prevTEMAVal)
        {
         trade.PositionClose(_Symbol);

常见问题

AMA 对噪声过滤较强,H1 回测中假突破次数比普通 MA 少约 30%,但仍建议叠加一根大周期过滤线再上实盘。
会。震荡市反手逻辑胜率明显下滑,底稿显示连续反手回撤可能超 15%,建议加 ATR 阈值才允许反手。
小布可自动加载 DEMA 交叉模板并跑出近 3 个月回测摘要,你只需圈定品种和周期就能看胜率与回撤。
必须先确认原单持仓方向再发平仓指令,反手单建议延后一根 K 线确认,避免点差滑点吃掉利润。
多为句柄在初始化前就被引用,确保在 OnInit 完成句柄创建,且品种有对应周期历史数据再取缓冲。