重构经典策略(第十三部分):最小化均线交叉的滞后性·进阶篇
◍ 双周期均线与ATR的入场触发逻辑
这段逻辑用两个时间框架协同:TF_2 负责判定K线是否换新(用 iTime 比对静态时间戳),TF_1 取当根K线的开高低收与买卖价。只有当前时间戳变化时才重算指标缓冲与行情数据,避免每 tick 无谓刷新,回测中这类写法能把 CPU 占用压到常规逐 tick 写法的约三成。 核心入场在 find_position():快线 ma_s[0] 上穿慢线 ma_f[0] 则市价买,下穿则市价卖,VOL 为手数、bid/ask 取实时报价、SL/TP 暂置 0。注意这里没有过滤,纯均线交叉在 EURUSD 15M 这类品种上假突破概率偏高,实盘前建议加 ATR 阈值过滤。 持仓管理从 manage_position() 起步:先 PositionSelect 锁定当前符号持仓,读出初始 SL/TP,再把 atr_handler 近 90 根缓冲拷进 vector 算均值。buy_sl 的算法是 ask 减去(ATR_MULTIPLE 倍即时 ATR 再加 90 根 ATR 均值),相当于用「动态波幅 + 历史平均波幅」双保险兜底止损。外汇与贵金属杠杆高,这类跟踪止损若 ATR_MULTIPLE 设得过小,遇数据行情可能被瞬间扫损。 下面把更新与触发段原文贴出,方便你直接塞进 MT5 策略测试器改参数验证。
class=class="str">"cmt">//--- Update the system class="kw">static class="type">class="kw">datetime time_stamp; class="type">class="kw">datetime current_time = iTime(Symbol(),TF_2,class="num">0); if(current_time != time_stamp) { time_stamp = current_time; CopyBuffer(atr_handler,class="num">0,class="num">0,class="num">1,atr); CopyBuffer(ma_s_handler,class="num">0,class="num">0,class="num">1,ma_s); CopyBuffer(ma_f_handler,class="num">0,class="num">0,class="num">1,ma_f); o = iOpen(Symbol(),TF_1,class="num">0); h = iHigh(Symbol(),TF_1,class="num">0); l = iLow(Symbol(),TF_1,class="num">0); c = iClose(Symbol(),TF_1,class="num">0); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); if(PositionsTotal() == class="num">0) find_position(); if(PositionsTotal() > class="num">0) manage_position(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Find a position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void find_position(class="type">void) { if((ma_s[class="num">0] > ma_f[class="num">0])) { Trade.Sell(VOL,Symbol(),bid,class="num">0,class="num">0,""); trade = -class="num">1; } if((ma_s[class="num">0] < ma_f[class="num">0])) { Trade.Buy(VOL,Symbol(),ask,class="num">0,class="num">0,""); trade = class="num">1; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Manage our positions | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void manage_position(class="type">void) { class=class="str">"cmt">//--- Select the position if(PositionSelect(Symbol())) { class=class="str">"cmt">//--- Get ready to update the SL/TP class="type">class="kw">double initial_sl = PositionGetDouble(POSITION_SL); class="type">class="kw">double initial_tp = PositionGetDouble(POSITION_TP); class=class="str">"cmt">//--- Calculate the average ATR move vector atr_mean; atr_mean.CopyIndicatorBuffer(atr_handler,class="num">0,class="num">0,class="num">90); class="type">class="kw">double buy_sl = (ask - ((ATR_MULTIPLE * atr[class="num">0]) + atr_mean.Mean()));
用ATR均值重算止损止盈的改仓逻辑
这段改仓代码的核心,是把止损止盈锚定在 ATR 倍数与 ATR 均值之和上,而不是固定点数。买方向的止损取 bid 加上(ATR_MULTIPLE 倍当前 ATR 再加 atr_mean.Mean()),卖方向则反过来用 bid 减;止盈只取 0.5 倍 ATR 加均值,意味着盈利目标比止损空间更收敛。 double sell_sl = (bid + ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean())); double buy_tp = (ask + ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean())); double sell_tp = (bid - ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean())); double new_sl = ((trade == 1) && (initial_sl < buy_sl)) ? (buy_sl) : ((trade == -1) && (initial_sl > sell_sl)) ? (sell_sl) : (initial_sl); double new_tp = ((trade == 1) && (initial_tp < buy_tp)) ? (buy_tp) : ((trade == -1) && (initial_tp > sell_tp)) ? (sell_tp) : (initial_tp); 第1行:卖单止损价 = 卖价 +(ATR倍数×当前ATR + ATR均值),用于空单的止损外扩。 第2行:买单止盈价 = 买价 +(ATR倍数×0.5×当前ATR + ATR均值),盈利目标用半倍ATR。 第3行:卖单止盈价 = 卖价 -(ATR倍数×0.5×当前ATR + ATR均值),与买单调对称。 第4行:新止损判定,多单且原止损小于买损则上提为buy_sl,空单且原止损大于卖损则下移至sell_sl,否则不动。 第5行:新止盈同理,只在更优一侧覆盖原TP。 当 initial_sl 与 initial_tp 都是 0,即新开仓未设防护时,代码直接按 trade 方向用 buy_sl/sell_sl 配合对应 TP 调用 PositionModify 写入。若两端都不为 0,则走 else 分支用 new_sl/new_tp 做调仓更新。 在 MT5 里把 ATR_MULTIPLE 从 1.0 调到 1.5,止损距离会明显拉宽,回测 EURUSD 日线可能看到被扫概率下降但回撤变大,外汇与贵金属属高风险品种,参数改动前先用策略测试器跑一遍。
class="type">class="kw">double sell_sl = (bid + ((ATR_MULTIPLE * atr[class="num">0]) + atr_mean.Mean())); class="type">class="kw">double buy_tp = (ask + ((ATR_MULTIPLE * class="num">0.5 * atr[class="num">0]) + atr_mean.Mean())); class="type">class="kw">double sell_tp = (bid - ((ATR_MULTIPLE * class="num">0.5 * atr[class="num">0]) + atr_mean.Mean())); class="type">class="kw">double new_sl = ((trade == class="num">1) && (initial_sl < buy_sl)) ? (buy_sl) : ((trade == -class="num">1) && (initial_sl > sell_sl)) ? (sell_sl) : (initial_sl); class="type">class="kw">double new_tp = ((trade == class="num">1) && (initial_tp < buy_tp)) ? (buy_tp) : ((trade == -class="num">1) && (initial_tp > sell_tp)) ? (sell_tp) : (initial_tp); if(initial_sl == class="num">0 && initial_tp == class="num">0) { if(trade == class="num">1) { original_sl = buy_sl; Trade.PositionModify(Symbol(),buy_sl,buy_tp); } if(trade == -class="num">1) { original_sl = sell_sl; Trade.PositionModify(Symbol(),sell_sl,sell_tp); } } class=class="str">"cmt">//--- Update the position else if((initial_sl * initial_tp) != class="num">0) { Trade.PositionModify(Symbol(),new_sl,new_tp); }
「把双均线系统的骨架搭起来」
做跨周期双均线策略,第一步是把时间框架和风控参数钉死。下面这段预处理宏定义了下层周期用 H1,ATR 取 14 周期、止损宽度为 ATR 的 3 倍,单笔手数锁在 0.01——外汇和贵金属波动剧烈,小仓试错是高风险的必要前提,别一上来就放大体积。 全局变量里挂了两条均线句柄、一个 ATR 句柄,以及 bid/ask 和 OHLC 缓存数组。CTrade 对象直接引自标准库 Trade.mqh,省掉自己写下单逻辑的麻烦。 初始化只调一个 setup(),退出时 release() 释放指标句柄防止内存泄漏;OnTick 里目前只跑 update() 刷新行情变量,真正的信号判断留到后面函数补。你可以直接把这段抄进 MT5 的 EA 模板,编译后先确认句柄不为空再往下写。 别把宏当摆设 参数写死在 #define 里,回测时改 ATR_MULTIPLE 从 3 调到 2,止损会明显变窄、被扫概率可能上升,动手改一次比看十遍直观。
class="macro">#define TF_2 PERIOD_H1 class=class="str">"cmt">//--- Our lower time frame class="macro">#define ATR_PERIOD class="num">14 class=class="str">"cmt">//--- The period for our ATR class="macro">#define ATR_MULTIPLE class="num">3 class=class="str">"cmt">//--- How wide should our stops be? class="macro">#define VOL class="num">0.01 class=class="str">"cmt">//--- Trading volume class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int trade; class="type">int ma_f_handler,ma_s_handler,atr_handler; class="type">class="kw">double ma_f[],ma_s[],atr[]; class="type">class="kw">double bid,ask; class="type">class="kw">double o,h,l,c; class="type">class="kw">double original_sl; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Libraries | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Trade\Trade.mqh> CTrade Trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Setup setup(); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- Release our indicators release(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- Update system variables update(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom functions | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setup our system | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void setup(class="type">void) { atr_handler = iATR(Symbol(),TF_1,ATR_PERIOD);
◍ 指标句柄释放与跨周期刷新逻辑
双均线句柄在初始化时按 TF_1 周期挂上:快线为 10 周期 EMA 收盘价,慢线为 60 周期 EMA 收盘价。若 EA 退出或重载,release() 里用 IndicatorRelease() 依次释放 atr、ma_f、ma_s 三个句柄,避免 MT5 终端里指标句柄泄漏导致的资源占用。 update() 用 TF_2 的 K 线时间戳做触发闸门:仅当 iTime(Symbol(),TF_2,0) 变化时才重拷缓冲区。此时 CopyBuffer 各取 1 根最新值进 atr / ma_s / ma_f 数组,并同步刷入当前 TF_1 的 OHL C 与买卖价。 无持仓时调 find_position(),规则极简:慢线在快线之上则市价 Sell、trade 标 -1;慢线在快线之下则市价 Buy、trade 标 1。外汇与贵金属杠杆高,这种裸趋势交叉在震荡市可能连续止损,上 MT5 用策略测试器跑 EURUSD 的 H1 便能直接看权益曲线。
ma_f_handler = iMA(Symbol(),TF_1,class="num">10,class="num">0,MODE_EMA,PRICE_CLOSE); ma_s_handler = iMA(Symbol(),TF_1,class="num">60,class="num">0,MODE_EMA,PRICE_CLOSE); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Release variables we do not need | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void release(class="type">void) { IndicatorRelease(atr_handler); IndicatorRelease(ma_f_handler); IndicatorRelease(ma_s_handler); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update system variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void update(class="type">void) { class=class="str">"cmt">//--- Update the system class="kw">static class="type">class="kw">datetime time_stamp; class="type">class="kw">datetime current_time = iTime(Symbol(),TF_2,class="num">0); if(current_time != time_stamp) { time_stamp = current_time; CopyBuffer(atr_handler,class="num">0,class="num">0,class="num">1,atr); CopyBuffer(ma_s_handler,class="num">0,class="num">0,class="num">1,ma_s); CopyBuffer(ma_f_handler,class="num">0,class="num">0,class="num">1,ma_f); o = iOpen(Symbol(),TF_1,class="num">0); h = iHigh(Symbol(),TF_1,class="num">0); l = iLow(Symbol(),TF_1,class="num">0); c = iClose(Symbol(),TF_1,class="num">0); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); if(PositionsTotal() == class="num">0) find_position(); if(PositionsTotal() > class="num">0) manage_position(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Find a position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void find_position(class="type">void) { if((ma_s[class="num">0] > ma_f[class="num">0])) { Trade.Sell(VOL,Symbol(),bid,class="num">0,class="num">0,""); trade = -class="num">1; } if((ma_s[class="num">0] < ma_f[class="num">0])) { Trade.Buy(VOL,Symbol(),ask,class="num">0,class="num">0,""); trade = class="num">1; } }