经典策略重塑(第12部分):欧元兑美元(EURUSD)突破交易策略·进阶篇
(2/3)· 把裸突破改成带倾向确认与 ATR 动态止损,过度交易和假突破该怎么砍掉
入场触发与偏见标记的代码片段
这段逻辑接在通道与均线计算之后,用 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 策略测试器用历史数据跑一遍验证。
)
{
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」会从经纪商拉大量数据,跑几分钟属正常,别中途关机。 结果并不乐观:平均盈利和平均亏损同幅度增加,盈利交易占比还略降,系统盈利能力反而被削弱。改动帮我们识别了部分噪音,但代价是过滤掉了一些本可盈利的单子。
<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,就能观察到通道线随波动重画、且快线金叉慢线后才有买单调用的行为。
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 观察同周期数值。若发现经常因为交叉盘不达标而漏单,说明原作者的「波动率医生」框架在震荡市会主动缩手——外汇和贵金属杠杆高,这种保守过滤倾向降低噪音入场概率,但也可能错过快行情。 下面把核心判定代码原样留下,方便你逐行对照改阈值:
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根。
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”标识,方便后续训练读取。
外汇与贵金属波动剧烈、杠杆风险高,机器学出的规则也只代表历史概率,实盘前请在策略测试器用不同年份数据交叉验证。
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% 概率走在收盘价外侧,可作为趋势过滤的粗判依据。外汇与贵金属杠杆高,样本结论仅作概率参考,实盘须自担风险。
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">//+------------------------------------------------------------------+