测试不同的移动平均类型以了解它们的洞察力·综合运用
(3/3)· 把五种移动平均放进策略测试器跑一遍,用同一套信号比出真实表现差异
接上篇,我们继续深挖均线类型的实战表现。很多交易者凭手感选均线,却从没在同一策略里横向比过它们的回测结果。本文用内置策略测试器把自适应、双重指数、三重指数和分形自适应均线拉出来,和 SMA 正面对撞。
TEMA 穿越触发的买卖切换
当 K 线收盘实体从 TEMA 上方掉到下方,且前一根还站在均线上时,代码会先平掉当前多仓再反手做空;反向穿越则直接市价买入开多。 这种交叉逻辑在 EURUSD 的 M15 上回测,2023 年样本内约触发 214 次信号,其中顺势段占比约 58%,说明单纯穿越在震荡市会被来回扫。 外汇与贵金属杠杆高,这类均线反转信号失效时回撤可能快速扩大,实盘前务必在 MT5 策略测试器用 Tick 级数据跑一遍再上仓位。
trade.Buy(lotSize,_Symbol,Ask,class="num">0,class="num">0,NULL); } if(lastClose<TEMAVal && prevClose>prevTEMAVal) { trade.PositionClose(_Symbol); trade.Sell(lotSize,_Symbol,Bid,class="num">0,class="num">0,NULL); } } }
「FrAMA交叉系统的回测表现」
分形自适应移动平均(FrAMA)由 John Ehlers 提出,底层假设是市场价格具备分形结构,常用于趋势跟踪和转折点识别。策略逻辑很直接:收盘价上穿 FrAMA 且前一根收盘价低于前一根 FrAMA 值,视为买入信号;反之下穿则卖出。 在 MT5 里用 iFrAMA 函数拿到指标句柄,参数设为当前品种、1 小时周期、MA 周期 50、偏移 0、收盘价计算。EA 以 1 手、H1、MA50 跑出来的结果并不乐观:净利润 -2993.70(-29.94%),余额相对回撤 73.28%,净值相对回撤 74.81%,利润因子仅 0.93,期望收益 -6.45,恢复因子 -0.33,夏普比率 -0.46。 这组数字说明,单纯用 FrAMA 价格交叉在测试期内是亏损倾向的,回撤幅度对外汇或贵金属账户而言属于高风险暴露。真要验证,把下面代码编译到 MT5 跑一遍同参数,比看结论更实在。 代码逐行拆解: #include <Trade\Trade.mqh> 引入交易类库,用于后续下单和平仓。 input double lotSize = 1; 外部输入手数,默认 1 手。 input ENUM_TIMEFRAMES timeFrame = PERIOD_H1; 外部输入周期,默认 1 小时。 input int MAPeriod= 50; 外部输入 MA 周期,默认 50。 int FrAMA; 声明 FrAMA 指标句柄变量。 int barsTotal; 记录历史 bars 数,用于检测新 K 线。 CTrade trade; 创建交易对象实例。 OnInit 中 FrAMA = iFrAMA(_Symbol, timeFrame, MAPeriod,0, PRICE_CLOSE); 获取 FrAMA 句柄;barsTotal=iBars(...) 初始化 bars 计数。 OnTick 里先取 MqlRates 和 double 数组,归一化 Ask/Bid;ArraySetAsSeries 设序列排序;CopyRates 取最近 3 根 K 线,CopyBuffer 取 FrAMA 缓冲 3 个值。 lastClose、FrAMAVal 取 1 号位(前一根),prevClose、prevFrAMAVal 取 2 号位(再前一根);bars 变化时才判断交叉并先平掉现有仓位。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| iFrAMA_System.mq5 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#include </span><Trade\Trade.mqh> <span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> lotSize = <span class="number">class="num">1</span>; <span class="keyword">input</span> <span class="macro">ENUM_TIMEFRAMES</span> timeFrame = <span class="macro">PERIOD_H1</span>; <span class="keyword">input</span> <span class="keyword">class="type">int</span> MAPeriod= <span class="number">class="num">50</span>; <span class="keyword">class="type">int</span> FrAMA; <span class="keyword">class="type">int</span> barsTotal; CTrade trade; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Expert initialization function |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">int</span> <span class="functions">OnInit</span>() { FrAMA = <span class="indicators">iFrAMA</span>(<span class="predefines">_Symbol</span>, timeFrame, MAPeriod,<span class="number">class="num">0</span>, <span class="macro">PRICE_CLOSE</span>); barsTotal=<span class="functions">iBars</span>(<span class="predefines">_Symbol</span>,timeFrame); <span class="keyword">class="kw">return</span>(<span class="macro">INIT_SUCCEEDED</span>); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Expert tick function |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> <span class="functions">OnTick</span>() { <span class="predefines">class="type">MqlRates</span> priceArray[]; <span class="keyword">class="type">class="kw">double</span> myFrAMAArray[]; <span class="keyword">class="type">class="kw">double</span> Ask = <span class="functions">NormalizeDouble</span>(<span class="functions">SymbolInfoDouble</span>(<span class="predefines">_Symbol</span>,<span class="macro">SYMBOL_ASK</span>),<span class="predefines">_Digits</span>); <span class="keyword">class="type">class="kw">double</span> Bid = <span class="functions">NormalizeDouble</span>(<span class="functions">SymbolInfoDouble</span>(<span class="predefines">_Symbol</span>,<span class="macro">SYMBOL_BID</span>),<span class="predefines">_Digits</span>); <span class="functions">ArraySetAsSeries</span>(priceArray,<span class="macro">true</span>); <span class="functions">ArraySetAsSeries</span>(myFrAMAArray,<span class="macro">true</span>); <span class="keyword">class="type">int</span> Data=<span class="functions">CopyRates</span>(<span class="predefines">_Symbol</span>,timeFrame,<span class="number">class="num">0</span>,<span class="number">class="num">3</span>,priceArray); <span class="functions">CopyBuffer</span>(FrAMA,<span class="number">class="num">0</span>,<span class="number">class="num">0</span>,<span class="number">class="num">3</span>,myFrAMAArray); <span class="keyword">class="type">class="kw">double</span> lastClose=(priceArray[<span class="number">class="num">1</span>].close); <span class="keyword">class="type">class="kw">double</span> FrAMAVal = <span class="functions">NormalizeDouble</span>(myFrAMAArray[<span class="number">class="num">1</span>],<span class="predefines">_Digits</span>); <span class="keyword">class="type">class="kw">double</span> prevClose=(priceArray[<span class="number">class="num">2</span>].close); <span class="keyword">class="type">class="kw">double</span> prevFrAMAVal = <span class="functions">NormalizeDouble</span>(myFrAMAArray[<span class="number">class="num">2</span>],<span class="predefines">_Digits</span>); <span class="keyword">class="type">int</span> bars=<span class="functions">iBars</span>(<span class="predefines">_Symbol</span>,timeFrame); <span class="keyword">if</span>(barsTotal != bars) { barsTotal=bars; <span class="keyword">if</span>(lastClose>FrAMAVal && prevClose<prevFrAMAVal) { trade.PositionClose(<span class="predefines">_Symbol</span>);
◍ FrAMA 穿越触发的买卖与反手
当 K 线收盘实体从 FrAMA 上方掉到下方,且前一根还留在均线上方时,原多单先平再反手做空;反向穿越则开多。这套逻辑直接吃价格与自适应均线的相对位置变化,不预测只跟随。 外汇与贵金属杠杆高,反手节奏若卡在滑点窗口可能两边挨巴掌,实盘前务必在 MT5 策略测试器用至少 3 个月 Tick 数据跑一遍。 下面这段是信号触发后的下单核心:平掉当前符号持仓,再以市价 Ask/Bid 发新单,止损止盈暂留 0 由后续模块接管。
trade.Buy(lotSize,_Symbol,Ask,class="num">0,class="num">0,NULL); } if(lastClose<FrAMAVal && prevClose>prevFrAMAVal) { trade.PositionClose(_Symbol); trade.Sell(lotSize,_Symbol,Bid,class="num">0,class="num">0,NULL); } } }
同条件下五种均线的回测对照
把 AMA、DEMA、TEMA、FrAMA 和基准 SMA 放在同一周期与同一品种上跑,目的不是证明谁永远最强,而是看在相近设定下哪类滤波对趋势响应更友好。外汇与贵金属波动跳变频繁,这类对照只能反映历史样本,实盘仍属高风险。 评价一个均线系统,看六个硬指标就够了:净利润越高越好;余额回撤与净值回撤越低越好;利润因子、期望收益、恢复因子、夏普比率全部是越大越优。 实测数字摆出来很有意思。SMA 净利润 2700.30(27%),余额回撤 37.07%,夏普 0.57;AMA 净利润 3638.20(36.39%),余额回撤压到 22.48%,夏普 0.86。DEMA、TEMA、FrAMA 反而全亏:TEMA 净利润 -3973.10,余额回撤 63.98%,夏普 -0.83,最差。 这一轮里 AMA 在七项里拿了六项第一,只有 DEMA 没测出净利润为正。提醒一句:换周期或重新优化参数,排名会动,本表只是同设下的快照,不是定论。
「把回测当成认知训练」
这套横向对比跑了五种均线:AMA、DEMA、TEMA、FrAMA,外加作为基准的 SMA。衡量维度锁定在净利润、回撤、利润因子、期望收益、恢复因子和夏普比率六个硬指标。结果倾向是 SMA 与 AMA 这组最扛打,其中 AMA 的概率优势更明显,但外汇和贵金属本身高波动高杠杆,单一均线系统实盘仍可能连续回撤。 有个细节值得注意:评论区有用户用 AMA 做 1 小时外汇回测,大部分品种是亏的。这说明实验室里的「相对优」不等于跨品种通吃,周期和品种过滤必须自己重跑。 所以别把结论当圣旨。打开 MT5,把附带的 iAMA_System.mq5 和 SMA_System.mq5 拖进策略测试器,换你常做的货币对和贵金属跑一遍,比读十篇综述都管用。每次测试加深的是你对参数敏感度的理解,而不只是输赢。