经典策略重塑(第12部分):欧元兑美元(EURUSD)突破交易策略(基础篇)
EURUSD H1 通道突破的触发与确认逻辑
在 MT5 里跑 EURUSD 的 H1 突破,第一步不是盯均线,而是先把前一根 K 线的最高价和最低价记下来,框出初始通道。系统刚加载时交易倾向记为 0,不认方向也不下单,只等价格把这根通道完全甩在身后。 所谓“完全走出”,要求下一根 K 线在通道外开盘并且收盘——此时才生成方向性倾向:收在上方偏多,下方偏空。但倾向只是候选,不是信号。真正开仓要等确认:做多时后续收盘价不能掉回倾向点下方,做空时不能弹回上方,否则这一波直接废掉。 单纯靠通道会过度交易,所以原文在倾向确认后接了双 EMA 过滤:5 周期 EMA 上穿 60 周期 EMA(都取收盘价)才做多,反之下穿做空;止损止盈用 14 周期 ATR 动态拉伸。回测窗口取 2020-01-01 至 2024-11-30,样本覆盖约 4.9 年 H1 数据,外汇品种杠杆高、滑点扰动大,任何倾向都只是概率而非确定性。
「通道突破策略的模块骨架与首轮回测败因」
这套基于波动通道的 EA 把逻辑拆成了几个硬模块:常量锁定均线周期与手数,全局变量存通道轨和方向倾向(bias),报价到来先刷全局变量再找机会。方向判定靠快/慢均线位置加价格关系——ma_f[0] > ma_s[0] 且价在快线上方倾向做多,反之做空;bias 取 1 / -1 / 0 表示多、空、无向。 下单被「确认」卡死:突破初始通道后,价格必须继续同向且不回破通道,confirmation 置 1 才真正发单。止损初值挂在通道反侧轨,之后用 ATR 倍数动态挪。作者把 EA 命名为 MTF Channel 2,在 EURUSD H1 用真实 tick + 随机延迟测了三轮,系统参数全程不动,只换交易规则。 首轮结果很直白:53 笔交易里 70% 亏损,夏普为负;好消息是平均盈利大于平均亏损。也就是说,出场和止损宽度可能是出血点,而非信号本身完全失效。外汇与贵金属属高风险品种,这类回测亏损不代表任何实盘倾向,仅说明规则有待收紧。 下面这段是策略头部的常量和交易库引入,先吃透它才能改参数: //+------------------------------------------------------------------+
| // | MTF Channel 2.mq5 |
|---|---|
| // | Gamuchirai Zororo Ndawana |
| // | [MQL5官方文档] |
//+------------------------------------------------------------------+
| // | Library |
|---|
//+------------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade Trade; //+------------------------------------------------------------------+
| // | Constants |
|---|
//+------------------------------------------------------------------+ const int ma_f_period = 5; //Slow MA const int ma_s_period = 60; //Slow MA //+------------------------------------------------------------------+
| // | Inputs |
|---|
//+------------------------------------------------------------------+ input group "Money Management" 逐行拆解:第 1–4 行是文件头注释,标明 EA 名与作者信息,无执行意义。第 7 行引入官方 Trade 库,第 8 行实例化 CTrade 对象供后续发单。第 11–14 行定义两个常量均线周期——快线 5、慢线 60,注释里把 ma_f_period 误写成 Slow MA,实际是快线参数。第 17 行起是输入参数分组声明,这里只开了「Money Management」组头,具体手数等在下文补全。 别把正态当圣经 回测 70% 亏损但平均盈利覆盖平均亏损,说明样本里存在少数大赚单拖着期望没塌。直接砍信号不如先调 ATR 止损倍数,MT5 里把通道轨初值改成 ATR(14)*1.5 试试可能少流血。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| MTF Channel class="num">2.mq5 | class=class="str">"cmt">//| Gamuchirai Zororo Ndawana | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Library | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Trade/Trade.mqh> CTrade Trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constants | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">const class="type">int ma_f_period = class="num">5; class=class="str">"cmt">//Slow MA class="kw">const class="type">int ma_s_period = class="num">60; class=class="str">"cmt">//Slow MA class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Inputs | class=class="str">"cmt">//+------------------------------------------------------------------+ input group "Money Management"
◍ EA骨架里的输入参数与生命周期钩子
把策略塞进MT5自动跑,先得把仓位系数和波动系数暴露成外部输入。下面这段把 lot_multiple 设成5、atr_multiple 设成5,意思是每笔手数取最小合约的5倍,止损距离参考5倍ATR,实战里你可以直接改这两个数看回测曲线怎么变。 全局区那一串 double/int 变量不是摆设:channel_high、channel_low 存前一根M30的极值,bias 和 confirmation 管方向判定,atr_handler、ma_fast、ma_slow 三个指标句柄必须在 OnInit 里建、OnDeinit 里用 IndicatorRelease 释放,否则反复加载EA会漏句柄。 OnInit 只调一个 setup() 就返回 INIT_SUCCEEDED,真正的指标抓取放在 setup 函数里:channel_high 取 M30 周期第1根(即已完成那根)最高价,channel_low 取同根最低价,vol 用 lot_multiple 乘 SYMBOL_VOLUME_MIN 得出。外汇和贵金属杠杆高,这种按最小合约整数倍下单的方式能压住爆仓概率,但仍属高风险。 OnTick 的逻辑很克制:有持仓就 manage_setup() 管风控;用 static datetime timestamp 比对当前K线时间,只在换根且空仓时调 find_setup() 找信号。这样避免每跳都重算,CPU占用和重绘乱闪都少。
input class="type">int lot_multiple = class="num">5; class=class="str">"cmt">//Lot Multiple input class="type">int atr_multiple = class="num">5; class=class="str">"cmt">//ATR Multiple class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global varaibles | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double channel_high = class="num">0; class="type">class="kw">double channel_low = class="num">0; class="type">class="kw">double o,h,l,c; class="type">int bias = class="num">0; class="type">class="kw">double bias_level = class="num">0; class="type">int confirmation = class="num">0; class="type">class="kw">double vol,bid,ask,initial_sl; class="type">int atr_handler,ma_fast,ma_slow; class="type">class="kw">double atr[],ma_f[],ma_s[]; class="type">class="kw">double bo_h,bo_l; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 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(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- IndicatorRelease(atr_handler); IndicatorRelease(ma_fast); IndicatorRelease(ma_slow); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- If we have positions open if(PositionsTotal() > class="num">0) manage_setup(); class=class="str">"cmt">//--- Keep track of time class="kw">static class="type">class="kw">datetime timestamp; class="type">class="kw">datetime time = iTime(Symbol(),PERIOD_CURRENT,class="num">0); if(timestamp != time) { class=class="str">"cmt">//--- Time Stamp timestamp = time; if(PositionsTotal() == class="num">0) find_setup(); } } class=class="str">"cmt">//+---------------------------------------------------------------+ class=class="str">"cmt">//| Load our technical indicators and market data | class=class="str">"cmt">//+---------------------------------------------------------------+ class="type">void setup(class="type">void) { channel_high = iHigh(Symbol(),PERIOD_M30,class="num">1); channel_low = iLow(Symbol(),PERIOD_M30,class="num">1); vol = lot_multiple * SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
通道与持仓的动态维护写法
这段逻辑负责把价格通道画出来,并在有持仓时按 ATR 动态移动止损。先创建两条水平线标记 channel_high 与 channel_low,再初始化 ATR(14) 与两条不同周期的 EMA 均线句柄,供后续判断趋势偏置使用。 update_channel() 每次收到新高低点就删掉旧水平线、按新值重画。通道不是一次性的,随着行情推进会被持续刷新,这是盯盘时视觉定位区间的关键。 manage_setup() 里先抓 bid/ask 和 ATR 缓冲,再用 PositionSelect() 找当前品种持仓并读取初始 SL。若 bias==1(偏多),新止损 = ask - ATR[0]*atr_multiple;仅当新 SL 高于原 SL 才调用 PositionModify 上移,避免止损被向下拉。偏空时对称处理用 bid + ATR[0]*atr_multiple。 外汇与贵金属杠杆高、滑点跳空常见,ATR 倍数止损只降低被扫概率,不保证不被反向击穿。把 atr_multiple 从 1.5 调到 2.0 在 MT5 里回看 EURUSD 小时图,能直观对比止损触发频率的变化。
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); atr_handler = iATR(Symbol(),PERIOD_CURRENT,class="num">14); ma_fast = iMA(Symbol(),PERIOD_CURRENT,ma_f_period,class="num">0,MODE_EMA,PRICE_CLOSE); ma_slow = iMA(Symbol(),PERIOD_CURRENT,ma_s_period,class="num">0,MODE_EMA,PRICE_CLOSE); } 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">//| Manage setup | class=class="str">"cmt">//+---------------------------------------------------------------+ class="type">void manage_setup(class="type">void) { bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); CopyBuffer(atr_handler,class="num">0,class="num">0,class="num">1,atr); Print("Managing Position"); if(PositionSelect(Symbol())) { Print("Position Found"); initial_sl = PositionGetDouble(POSITION_SL); } if(bias == class="num">1) { Print("Position Buy"); class="type">class="kw">double new_sl = (ask - (atr[class="num">0] * atr_multiple)); Print("Initial: ",initial_sl,"\nNew: ",new_sl); if(initial_sl < new_sl) { Trade.PositionModify(Symbol(),new_sl,class="num">0); Print("DONE"); } } if(bias == -class="num">1) { Print("Position Sell"); class="type">class="kw">double new_sl = (bid + (atr[class="num">0] * atr_multiple)); Print("Initial: ",initial_sl,"\nNew: ",new_sl);
「用整根K线确认通道外的偏见」
在 bias == 0 的空白状态下,系统只认「整根已完成 K线」四价全在通道外,才给市场贴多头或空头标签。代码取的是 PERIOD_CURRENT 的 1 号 Bar(已收盘的那根),open/high/low/close 同时大于 channel_high 才把 bias 置 1,并记录 bias_level = h;四价同时小于 channel_low 则 bias = -1,bias_level = l。
这种写法的坑在于:它用上一根完整 K线说话,当前 tick 的毛刺不会误触发。你在 MT5 里把 channel_high/low 打印到图表,会看到偏见只在实体完全脱离通道后生成,盘中瞬破不算数。
偏见生成后并非永久有效。若已处空头 bias(-1),下一根完整 K线四价又全高于 channel_high,就调 forget_bias() 清掉;多头被反向四价全破也同样作废。外汇与贵金属杠杆高,通道参数错配会让偏见频繁翻转,实盘前先用历史数据数一下每日翻转次数。
if(initial_sl > new_sl) { Trade.PositionModify(Symbol(),new_sl,class="num">0); Print("DONE"); } } } 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">//--- We are updating the system o = iOpen(Symbol(),PERIOD_CURRENT,class="num">1); h = iHigh(Symbol(),PERIOD_CURRENT,class="num">1); l = iLow(Symbol(),PERIOD_CURRENT,class="num">1); c = iClose(Symbol(),PERIOD_CURRENT,class="num">1); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); CopyBuffer(atr_handler,class="num">0,class="num">0,class="num">1,atr); CopyBuffer(ma_fast,class="num">0,class="num">0,class="num">1,ma_f); CopyBuffer(ma_slow,class="num">0,class="num">0,class="num">1,ma_s); class=class="str">"cmt">//--- If we have no market bias if(bias == class="num">0) { class=class="str">"cmt">//--- Our bias is bullish if ( (o > channel_high) && (h > channel_high) && (l > channel_high) && (c > channel_high) ) { bias = class="num">1; bias_level = h; bo_h = h; bo_l = l; mark_bias(h); } class=class="str">"cmt">//--- Our bias is bearish if ( (o < channel_low) && (h < channel_low) && (l < channel_low) && (c < channel_low) ) { bias = -class="num">1; bias_level = l; bo_h = h; bo_l = l; mark_bias(l); } } class=class="str">"cmt">//--- Is our bias valid? if(bias != class="num">0) { class=class="str">"cmt">//--- Our bearish bias has been violated if ( (o > channel_high) && (h > channel_high) && (l > channel_high) && (c > channel_high) && (bias == -class="num">1) ) { forget_bias(); } class=class="str">"cmt">//--- Our bullish bias has been violated if (
◍ 偏置失效与确认信号的清退逻辑
当价格四价(开高低收)全部跌穿通道下轨且偏置标记为多头(bias==1)时,系统调用 forget_bias() 清除方向倾向。这说明即便前面给了看涨依据,只要整根 K 线游离在通道外,概率上原偏置已失效,不宜硬扛。 另一种失效来自刺穿通道:若开盘低于通道上轨但收盘高于通道下轨,同样触发 forget_bias()。这类“下破上收”的毛刺在黄金 1 小时图常出现在数据行情,容易洗掉假突破单。 确认环节只在 confirmation==0 且 bias!=0 时检查。多头确认要求四价全在 bias_level 之上且 bias==1;空头则全在之下且 bias==-1,满足后 confirmation 置 1。 已确认状态下,多头若四价重回 bias_level 下方,confirmation 归零;空头若四价站上 bias_level,同样丢失确认。外汇与贵金属杠杆高,这种回破往往伴随滑点,实盘需把 bias_level 留足缓冲再跟。
if ( (o < channel_low) && (h < channel_low) && (l < channel_low) && (c < channel_low) && (bias == class="num">1) ) { forget_bias(); } class=class="str">"cmt">//--- Our bullish bias has been violated if ( ((o < channel_high) && (c > channel_low)) ) { forget_bias(); } class=class="str">"cmt">//--- Check if we have confirmation if((confirmation == class="num">0) && (bias != class="num">0)) { class=class="str">"cmt">//--- Check if we are above the bias level if ( (o > bias_level) && (h > bias_level) && (l > bias_level) && (c > bias_level) && (bias == class="num">1) ) { confirmation = class="num">1; } class=class="str">"cmt">//--- Check if we are below the bias level if ( (o < bias_level) && (h < bias_level) && (l < bias_level) && (c < bias_level) && (bias == -class="num">1) ) { confirmation = class="num">1; } } class=class="str">"cmt">//--- Check if our confirmation is still valid if(confirmation == class="num">1) { class=class="str">"cmt">//--- Our bias is bullish if(bias == class="num">1) { class=class="str">"cmt">//--- Confirmation is lost if we fall beneath the breakout level if ( (o < bias_level) && (h < bias_level) && (l < bias_level) && (c < bias_level) ) { confirmation = class="num">0; } } class=class="str">"cmt">//--- Our bias is bearish if(bias == -class="num">1) { class=class="str">"cmt">//--- Confirmation is lost if we rise above the breakout level if ( (o > bias_level) && (h > bias_level) && (l > bias_level) && (c > bias_level)