测试不同的移动平均类型以了解它们的洞察力·进阶篇
「用 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。OnInit里iAMA(...)拿指标句柄,偏移 0、价型 PRICE_CLOSE;iBars记初始柱数。OnTick中ArraySetAsSeries(...,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 自己跑一遍最实在。
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 数据看信号密度再上实盘。
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 交叉在样本内已亏,实盘前务必自测多品种多周期。
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 代表不设置。
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 以下或叠加强弱过滤再验。
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);