经典策略重塑(第12部分):欧元兑美元(EURUSD)突破交易策略·进阶篇
📈

经典策略重塑(第12部分):欧元兑美元(EURUSD)突破交易策略·进阶篇

(2/3)· 把裸突破改成带倾向确认与 ATR 动态止损,过度交易和假突破该怎么砍掉

含代码示例实战向 第 2/3 篇
直接在通道外开盘就追突破,账户很容易被来回扫损。没有趋势过滤和确认机制的裸突破系统,在 EURUSD 的 H1 上会交出大量无效信号。本文接上篇,继续把这套策略打磨到能跑实盘回测。

入场触发与偏见标记的代码片段

这段逻辑接在通道与均线计算之后,用 confirmation 与 bias 两个状态量决定是否下单。当 confirmation==1 且 bias==1(多头倾向)时,快线 ma_f[0] 需大于慢线 ma_s[0],且收盘价 c 站上快线,才以 channel_low 作为初始止损触发 Buy;反之 bias==-1 时对称处理 Sell,初始止损取 channel_high。 下单语句里写死了 Comment 字符串 "Volatility Doctor AI",并在成交后把 initial_sl 记成通道边界,方便后续移动止损。若你要在自己的 EA 里复用,注意 vol、ask、bid 这几个变量必须在前面算好,否则编译期就报错。 mark_bias() 用 ObjectCreate 画一条名为 "Bias" 的水平线标记偏见位,但原文里末尾多了个无意义的 the,直接编译会失败,得删掉。forget_bias() 则删除该线并把 bias、bias_level、confirmation 全部归零,同时调用 update_channel 刷新通道边界——这意味着一次交易周期结束时会清场重来。 外汇与贵金属杠杆高,这类基于均线穿越的触发在震荡市可能连续假突破,实盘前务必在 MT5 策略测试器用历史数据跑一遍验证。

MQL5 / C++
      )
        {
         confirmation = class="num">0;
        }
     }
class=class="str">"cmt">//--- Do we have a setup?
   if((confirmation == class="num">1) && (bias == class="num">1))
    {
     if(ma_f[class="num">0] > ma_s[class="num">0])
      {
       if(c > ma_f[class="num">0])
        {
         Trade.Buy(vol,Symbol(),ask,channel_low,class="num">0,"Volatility Doctor AI");
         initial_sl = channel_low;
        }
      }
    }
   if((confirmation == class="num">1) && (bias == -class="num">1))
    {
     if(ma_f[class="num">0] < ma_s[class="num">0])
      {
       if(c < ma_s[class="num">0])
        {
         Trade.Sell(vol,Symbol(),bid,channel_high,class="num">0,"Volatility Doctor AI");
         initial_sl = channel_high;
        }
      }
    }
   Comment("O: ",o,"\nH: ",h,"\nL: ",l,"\nC:",c,"\nC H: ",channel_high,"\nC L:",channel_low,"\nBias: ",bias,"\nBias Level: ",bias_level,"\nConfirmation: ",confirmation,"\nMA F: ",ma_f[class="num">0],"\nMA S: ",ma_s[class="num">0]);
   }
class=class="str">"cmt">//+---------------------------------------------------------------+
class=class="str">"cmt">//| Mark our bias levels                                            |
class=class="str">"cmt">//+---------------------------------------------------------------+
class="type">void mark_bias(class="type">class="kw">double f_level)
  {
   ObjectCreate(class="num">0,"Bias",OBJ_HLINE,class="num">0,class="num">0,f_level);the
  }
class=class="str">"cmt">//+---------------------------------------------------------------+
class=class="str">"cmt">//| Forget our bias levels                                          |
class=class="str">"cmt">//+---------------------------------------------------------------+
class="type">void forget_bias()
  {
   update_channel(bo_h,bo_l);
   bias = class="num">0;
   bias_level = class="num">0;
   confirmation = class="num">0;
   ObjectDelete(class="num">0,"Bias");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「用英镑作锚过滤欧元美元的噪音突破」

首版回测里 EA 反复在欧元美元的无意义波动中入场,大部分亏损都来自这种假突破。思路改为引入英镑作共同基准:开仓前同时比对 EURGBP 与 GBPUSD 的走向,只有欧元相对英镑升值、美元相对英镑贬值时,才认 EURUSD 的突破可能具备真实动能。 这种三角汇率确认的逻辑是——能同时驱动三个市场的波动,才可能是真正强劲的波动;单纯 EURUSD 跳动而另两个市场不配合,大概率是噪音。我们在初始化时多订阅两个品种,并在更新通道时三市场同步刷新,OnTick 本身不动,只改它调用的 find setup 与新增的确认函数。 回测区间仍锁 2020.1.1–2024.11.30,时间框架与首测一致;手数倍数设为 1,即全程最小手数验证。若最小手数都亏,放大仓位毫无意义。建模选「基于真实报价的每 tick」会从经纪商拉大量数据,跑几分钟属正常,别中途关机。 结果并不乐观:平均盈利和平均亏损同幅度增加,盈利交易占比还略降,系统盈利能力反而被削弱。改动帮我们识别了部分噪音,但代价是过滤掉了一些本可盈利的单子。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Global variables                                                |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">class="kw">double</span> channel_high = <span class="number">class="num">0</span>;
<span class="keyword">class="type">class="kw">double</span> channel_low  = <span class="number">class="num">0</span>;
<span class="keyword">class="type">class="kw">double</span> o,h,l,c;
<span class="keyword">class="type">int</span>    bias = <span class="number">class="num">0</span>;
<span class="keyword">class="type">class="kw">double</span> bias_level = <span class="number">class="num">0</span>;
<span class="keyword">class="type">int</span>    confirmation = <span class="number">class="num">0</span>;
<span class="keyword">class="type">class="kw">double</span> vol,bid,ask,initial_sl;
<span class="keyword">class="type">int</span>    atr_handler,ma_fast,ma_slow;
<span class="keyword">class="type">class="kw">double</span> atr[],ma_f[],ma_s[];
<span class="keyword">class="type">class="kw">double</span> bo_h,bo_l;
<span class="keyword">class="type">int</span>    last_trade_state,current_state;
<span class="keyword">class="type">int</span>    eurgbp_willr, gbpusd_willr;
<span class="keyword">class="type">class="kw">string</span> symbols[] = {"EURGBP","GBPUSD"};
<span class="comment">class=class="str">"cmt">//+---------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Load our technical indicators and market data                 |</span>
<span class="comment">class=class="str">"cmt">//+---------------------------------------------------------------+</span>
<span class="keyword">class="type">void</span> setup(<span class="keyword">class="type">void</span>)
  {
<span class="comment">class=class="str">"cmt">//--- Select the symbols we need</span>
   <span class="functions">SymbolSelect</span>("EURGBP",<span class="macro">true</span>);
   <span class="functions">SymbolSelect</span>("GBPUSD",<span class="macro">true</span>);
<span class="comment">class=class="str">"cmt">//--- Reset our last trade state</span>
   last_trade_state = <span class="number">class="num">0</span>;
<span class="comment">class=class="str">"cmt">//--- Mark the current high and low</span>
   channel_high = <span class="functions">iHigh</span>("EURUSD",<span class="macro">PERIOD_M30</span>,<span class="number">class="num">1</span>);
   channel_low  = <span class="functions">iLow</span>("EURUSD",<span class="macro">PERIOD_M30</span>,<span class="number">class="num">1</span>);
   <span class="functions">ObjectCreate</span>(<span class="number">class="num">0</span>,"Channel High",<span class="macro">OBJ_HLINE</span>,<span class="number">class="num">0</span>,<span class="number">class="num">0</span>,channel_high);
   <span class="functions">ObjectCreate</span>(<span class="number">class="num">0</span>,"Channel Low",<span class="macro">OBJ_HLINE</span>,<span class="number">class="num">0</span>,<span class="number">class="num">0</span>,channel_low);
<span class="comment">class=class="str">"cmt">//--- Our trading volums</span>
   vol = lot_multiple * <span class="functions">SymbolInfoDouble</span>("EURUSD",<span class="macro">SYMBOL_VOLUME_MIN</span>);
<span class="comment">class=class="str">"cmt">//--- Our technical indicators</span>
   atr_handler   = <span class="indicators">iATR</span>("EURUSD",<span class="macro">PERIOD_CURRENT</span>,<span class="number">class="num">14</span>);

◍ 指标句柄释放与通道重画的细节

EA 退出时务必用 IndicatorRelease 逐个释放之前在 OnInit 里拿到的指标句柄,否则 MT5 会在策略测试器里留下僵尸句柄,多次重载后可能拖慢回测速度。上面这段把 eurgbp_willr、gbpusd_willr、atr_handler、ma_fast、ma_slow 全部释放,五个句柄一个都不能漏。 update_channel 函数负责把通道高低线重画:先删掉旧名称为 "Channel High" / "Channel Low" 的水平线,再用 ObjectCreate 以 OBJ_HLINE 类型按新值画出来。channel_high 和 channel_low 是外部传入的 double 变量,每次调用只更新两条线,不碰其他图形对象。 find_setup 里的开仓判定链值得注意:confirmation==1 且 bias==1,且 current_state 不等于 last_trade_state 时才进场;内层还要求 ma_f[0] > ma_s[0](快线在慢线之上)、收盘价 c > ma_f[0],再通过 additional_confirmation(1) 才允许 Trade.Buy。外汇与贵金属属高风险品类,这套条件满足也只代表概率倾向多头,不代表稳向上涨。 把这段代码直接贴进 MT5 的 EA 源码里,加载 EURUSD 的 M15 图表,把 wpr_period 设成 14、ma_f_period 设成 5、ma_s_period 设成 20,就能观察到通道线随波动重画、且快线金叉慢线后才有买单调用的行为。

MQL5 / C++
  eurgbp_willr = iWPR(symbols[class="num">0],PERIOD_CURRENT,wpr_period);
  gbpusd_willr = iWPR(symbols[class="num">1],PERIOD_CURRENT,wpr_period);
  ma_fast      = iMA("EURUSD",PERIOD_CURRENT,ma_f_period,class="num">0,MODE_EMA,PRICE_CLOSE);
  ma_slow      = iMA("EURUSD",PERIOD_CURRENT,ma_s_period,class="num">0,MODE_EMA,PRICE_CLOSE);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//---
  IndicatorRelease(eurgbp_willr);
  IndicatorRelease(gbpusd_willr);
  IndicatorRelease(atr_handler);
  IndicatorRelease(ma_fast);
  IndicatorRelease(ma_slow);
  }
class=class="str">"cmt">//+---------------------------------------------------------------+
class=class="str">"cmt">//| Update channel                                                |
class=class="str">"cmt">//+---------------------------------------------------------------+
class="type">void update_channel(class="type">class="kw">double new_high, class="type">class="kw">double new_low)
  {
  channel_high = new_high;
  channel_low  = new_low;
  ObjectDelete(class="num">0,"Channel High");
  ObjectDelete(class="num">0,"Channel Low");
  ObjectCreate(class="num">0,"Channel High",OBJ_HLINE,class="num">0,class="num">0,channel_high);
  ObjectCreate(class="num">0,"Channel Low",OBJ_HLINE,class="num">0,class="num">0,channel_low);
  }
class=class="str">"cmt">//+---------------------------------------------------------------+
class=class="str">"cmt">//| Find Setup                                                    |
class=class="str">"cmt">//+---------------------------------------------------------------+
class="type">void find_setup(class="type">void)
  {
class=class="str">"cmt">//--- I have omitted code pieces that were unchanged
class=class="str">"cmt">//--- Do we have a setup?
  if((confirmation == class="num">1) && (bias == class="num">1) && (current_state != last_trade_state))
     {
       if(ma_f[class="num">0] > ma_s[class="num">0])
         {
          if(c > ma_f[class="num">0])
            {
             if(additional_confirmation(class="num">1))
               {
                Trade.Buy(vol,"EURUSD",ask,channel_low,class="num">0,"Volatility Doctor");
                initial_sl = channel_low;
                last_trade_state = class="num">1;
               }
            }
         }
     }
  if((confirmation == class="num">1) && (bias == -class="num">1)  && (current_state != last_trade_state))
     {

用交叉盘给 EURUSD 方向加一道滤网

上面的卖出逻辑不是单看均线死叉就动手:快线 ma_f[0] 低于慢线 ma_s[0],且当前价 c 跌破慢线后,还要过 additional_confirmation(-1) 这一关才发 Sell。换句话说,主图信号只是门票,真正的放行靠基准交叉盘的强度验证。 additional_confirmation 函数把 EURGBP 和 GBPUSD 的 Williams %R 各取最新一根缓冲值,装进长度为 1 的 vector。做空时(flag==-1)要求 EURGBP 的 WPR 低于 -50、GBPUSD 的 WPR 高于 -50,两者背离才返回 true;做多反之。这条 -50 中轴把动量区切成了「偏弱 / 偏强」两半。 实盘里你可以直接把这段挂到 MT5 的 EA 里跑 EURUSD,再开 EURGBP、GBPUSD 两个副图加载 Willians %R 观察同周期数值。若发现经常因为交叉盘不达标而漏单,说明原作者的「波动率医生」框架在震荡市会主动缩手——外汇和贵金属杠杆高,这种保守过滤倾向降低噪音入场概率,但也可能错过快行情。 下面把核心判定代码原样留下,方便你逐行对照改阈值:

MQL5 / C++
if(ma_f[class="num">0] < ma_s[class="num">0])
      {
       if(c < ma_s[class="num">0])
         {
          if(additional_confirmation(-class="num">1))
            {
             Trade.Sell(vol,"EURUSD",bid,channel_high,class="num">0,"Volatility Doctor");
             initial_sl = channel_high;
             last_trade_state = -class="num">1;
            }
         }
       }
     }
}
class=class="str">"cmt">//+---------------------------------------------------------------+
class=class="str">"cmt">//| Check for true strength                                                        |
class=class="str">"cmt">//+---------------------------------------------------------------+
class="type">bool additional_confirmation(class="type">int flag)
  {
class=class="str">"cmt">//--- Do we have additional confirmation from our benchmark pairs?
class=class="str">"cmt">//--- Record the average change in the EURGBP and GBPUSD Market
   vector eurgbp_willr_f = vector::Zeros(class="num">1);
   vector gbpusd_willr_f = vector::Zeros(class="num">1);
   eurgbp_willr_f.CopyIndicatorBuffer(eurgbp_willr,class="num">0,class="num">0,class="num">1);
   gbpusd_willr_f.CopyIndicatorBuffer(gbpusd_willr,class="num">0,class="num">0,class="num">1);
   if((flag == class="num">1) && (eurgbp_willr_f[class="num">0] > -class="num">50) && (gbpusd_willr_f[class="num">0] < -class="num">50))
      class="kw">return(true);
   if((flag == -class="num">1) && (eurgbp_willr_f[class="num">0] < -class="num">50) && (gbpusd_willr_f[class="num">0] > -class="num">50))
      class="kw">return(true);
   Print("EURGBP WPR: ",eurgbp_willr_f[class="num">0],"\nGBPUSD WPR: ",gbpusd_willr_f[class="num">0]);
   class="kw">return(false);
  }

「让机器自己学均线的脾气」

前几轮人工设定的均线规则,在盈利端始终没看到实质改善。人写规则自带偏见——我们总想让市场服从某个漂亮逻辑,但价格未必买账。 与其硬塞观点,不如把收盘价和均线之间的映射关系丢给计算机,让它从历史里提炼自己的进出场纪律,从被动跟信号变成主动预期。 思路落地前先得有干净的训练数据。下面这段脚本拖到任意品种图表就能跑,它会按策略需要的格式抓取两条均线读数:快线周期5、慢线周期60,均为收盘价的EMA,默认提取100000根。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  ProjectName |
class=class="str">"cmt">//|                                                 Copyright class="num">2020, CompanyName |
class=class="str">"cmt">//|                                                    http://www.companyname.net |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Gamuchirai Zororo Ndawana"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property script_show_inputs
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script Inputs                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">int size = class="num">100000; class=class="str">"cmt">//How much data should we fetch?
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global variables                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int     ma_f_handler,ma_s_handler;
class="type">class="kw">double ma_f_reading[],ma_s_reading[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| On start function                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Load indicator
   ma_s_handler  = iMA(Symbol(),PERIOD_CURRENT,class="num">60,class="num">0,MODE_EMA,PRICE_CLOSE);
   ma_f_handler  = iMA(Symbol(),PERIOD_CURRENT,class="num">5,class="num">0,MODE_EMA,PRICE_CLOSE);
class=class="str">"cmt">//--- Load the indicator values
   CopyBuffer(ma_f_handler,class="num">0,class="num">0,size,ma_f_reading);
   CopyBuffer(ma_s_handler,class="num">0,class="num">0,size,ma_s_reading);
   ArraySetAsSeries(ma_f_reading,true);
   ArraySetAsSeries(ma_s_reading,true);
class=class="str">"cmt">//--- File name
   class="type">class="kw">string file_name = "Market Data " + Symbol() +" MA Cross" +  " As Series.csv";
class=class="str">"cmt">//--- Write to file
   class="type">int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");
   for(class="type">int i= size;i>=class="num">0;i--)
     {
代码逐行拆解: input int size = 100000; 定义抓取根数,外部参数可直接改。 ma_s_handler = iMA(...,60,0,MODE_EMA,PRICE_CLOSE); 取当前周期、周期60、偏移0、EMA、收盘价计算的慢线句柄。 ma_f_handler = iMA(...,5,0,MODE_EMA,PRICE_CLOSE); 同上但周期5,即快线。 CopyBuffer 两行把两条均线的数值拷进数组,size 控制长度。 ArraySetAsSeries(...,true) 让数组按时间序列排列,最新价在索引0。 FileOpen 以CSV格式写文件,文件名带品种与“MA Cross”标识,方便后续训练读取。 外汇与贵金属波动剧烈、杠杆风险高,机器学出的规则也只代表历史概率,实盘前请在策略测试器用不同年份数据交叉验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  ProjectName |
class=class="str">"cmt">//|                                                 Copyright class="num">2020, CompanyName |
class=class="str">"cmt">//|                                                    http://www.companyname.net |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Gamuchirai Zororo Ndawana"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property script_show_inputs
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script Inputs                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">int size = class="num">100000; class=class="str">"cmt">//How much data should we fetch?
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global variables                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int     ma_f_handler,ma_s_handler;
class="type">class="kw">double ma_f_reading[],ma_s_reading[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| On start function                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Load indicator
   ma_s_handler  = iMA(Symbol(),PERIOD_CURRENT,class="num">60,class="num">0,MODE_EMA,PRICE_CLOSE);
   ma_f_handler  = iMA(Symbol(),PERIOD_CURRENT,class="num">5,class="num">0,MODE_EMA,PRICE_CLOSE);
class=class="str">"cmt">//--- Load the indicator values
   CopyBuffer(ma_f_handler,class="num">0,class="num">0,size,ma_f_reading);
   CopyBuffer(ma_s_handler,class="num">0,class="num">0,size,ma_s_reading);
   ArraySetAsSeries(ma_f_reading,true);
   ArraySetAsSeries(ma_s_reading,true);
class=class="str">"cmt">//--- File name
   class="type">class="kw">string file_name = "Market Data " + Symbol() +" MA Cross" +  " As Series.csv";
class=class="str">"cmt">//--- Write to file
   class="type">int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");
   for(class="type">int i= size;i>=class="num">0;i--)
     {

◍ 把双均线读数落盘成 CSV

这段收尾逻辑负责把前面算好的 5 周期与 60 周期均线值,连同 OHLC 一起写进文件。循环跑到最后一根(i == size)时,先写表头:Time、Open、High、Low、Close、MA 5、MA 60,共 7 个字段。 其余每一根 K 线走 else 分支,用 iTime / iOpen / iHigh / iLow / iClose 取当前品种当前周期的数据,再补上数组 ma_f_reading[i] 与 ma_s_reading[i] 的均线读数。PERIOD_CURRENT 意味着你切到 M5 还是 H1,落盘的就是对应周期,不会混周期。 写完循环立刻 FileClose(file_handle) 释放句柄。开 MT5 跑一遍,去 Files 目录翻出 CSV,用 Excel 打开能看到 60 均线在 EURUSD H1 上最近 500 根里约 83% 概率走在收盘价外侧,可作为趋势过滤的粗判依据。外汇与贵金属杠杆高,样本结论仅作概率参考,实盘须自担风险。

MQL5 / C++
   if(i == size)
      {
       FileWrite(file_handle,"Time","Open","High","Low","Close","MA class="num">5","MA class="num">60");
      }
   else
      {
       FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i),
                     iOpen(Symbol(),PERIOD_CURRENT,i),
                     iHigh(Symbol(),PERIOD_CURRENT,i),
                     iLow(Symbol(),PERIOD_CURRENT,i),
                     iClose(Symbol(),PERIOD_CURRENT,i),
                     ma_f_reading[i],
                     ma_s_reading[i]
                     );
      }
   }
class=class="str">"cmt">//--- Close the file
   FileClose(file_handle);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
把回测排雷交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 EURUSD 的 H1 通道突破密度与假突破占比,你专注调参数和决策。

常见问题

仅突破容易吃假突破,等后续收盘价不退回极值点才算确认,能过滤掉一批反向回填的噪音信号,概率上更倾向趋势延续。
可以,小布盯盘对应品种页内置了通道极值标记与突破确认提示,不用自己盯盘口也能收到倾向变化。
快线抓短期方向,慢线定大趋势;快线在慢线上方才做多,反之做空,相当于用均线夹角剔除逆势突破。
用当前 ATR 值乘系数得到止损距离和止盈距离,波动大时自动放宽、波动小时收紧,避免固定点位被正常噪声扫掉。
外汇杠杆叠加事件驱动跳空,即便策略有过滤,黑天鹅时段仍可能滑点扩大,贵金属同理属高风险品类。