创建一个基于日波动区间突破策略的 MQL5 EA·综合运用
(3/3)· 把前兩篇的模块拼成可跑的 EA,并解决假突破与止损 placement 的真实麻烦
◍ 给区间突破打标记的代码落点
在 MT5 里把突破点画上图表,本质就是给箭头对象设属性再补一段文字标签。下面这段逻辑承接前文的区间判定:当收盘价跌破当日区间下沿且在允许突破时段内,就触发标记绘制。 调用 drawBreakPoint 时传入了具体参数:箭头代码 233(对应 Wingdings 字体里的特定符号)、颜色 clrBlue、方向 1 表示向下突破。外汇与贵金属品种波动剧烈,这类标记仅作视觉提示,实盘仍需结合止损,杠杆交易风险偏高。 代码先通过 ObjectSetInteger 给箭头设箭头码、颜色、字号 12,再按 direction 正负决定锚点在顶部还是底部。随后用 OBJ_TEXT 建一个“ Break”文字对象,同样设色与字号,并根据方向选 ANCHOR_LEFT_UPPER 或 ANCHOR_LEFT_LOWER 贴着箭头放。最后 ChartRedraw(0) 强制重绘。 下半段是下破的判定分支:barClose < minimum_price 且时段合法、尚未标记过,就 Print 日志、置 isHaveRangeBreak=true,并用 233/clrBlue/1 调 drawBreakPoint 画点。复制进 EA 的 OnCalculate 尾段即可在回测里看到蓝色破位箭头。
class=class="str">"cmt">//--- Set the arrow&class="macro">#x27;s code(symbol) ObjectSetInteger(class="num">0, objName, OBJPROP_ARROWCODE, arrCode); class=class="str">"cmt">//--- Set the class="type">class="kw">color for the arrow ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clr); class=class="str">"cmt">//--- Set the font size for the arrow ObjectSetInteger(class="num">0, objName, OBJPROP_FONTSIZE, class="num">12); class=class="str">"cmt">//--- Set the anchor position for the arrow based on the direction if (direction > class="num">0) ObjectSetInteger(class="num">0, objName, OBJPROP_ANCHOR, ANCHOR_TOP); if (direction < class="num">0) ObjectSetInteger(class="num">0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM); class=class="str">"cmt">//--- Define a text label for the break point class="type">class="kw">string txt = " Break"; class="type">class="kw">string objNameDescr = objName + txt; class=class="str">"cmt">//--- Create a text object for the break point description ObjectCreate(class="num">0, objNameDescr, OBJ_TEXT, class="num">0, time, price); class=class="str">"cmt">//--- Set the class="type">class="kw">color for the text description ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_COLOR, clr); class=class="str">"cmt">//--- Set the font size for the text ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_FONTSIZE, class="num">12); class=class="str">"cmt">//--- Adjust the text anchor based on the direction of the arrow if (direction > class="num">0) { ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); ObjectSetString(class="num">0, objNameDescr, OBJPROP_TEXT, " " + txt); } if (direction < class="num">0) { ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); ObjectSetString(class="num">0, objNameDescr, OBJPROP_TEXT, " " + txt); } } class=class="str">"cmt">//--- Redraw the chart to reflect the new objects ChartRedraw(class="num">0); } class=class="str">"cmt">//--- Check for lower range breakout condition else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak && barTime >= validBreakTime_start && barTime <= validBreakTime_end){ Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); class=class="str">"cmt">//--- Log the breakout event isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">233,clrBlue,class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout }
日内区间突破的挂单与标记逻辑
在 MT5 EA 里处理突破信号,第一步是把当前报价规整到品种精度:用 SymbolInfoDouble 取 SYMBOL_ASK / SYMBOL_BID,再套 NormalizeDouble 按 _Digits 截断,避免浮点误差导致下单价非法。 上破条件要求收盘价 barClose 大于当日统计的 maximum_price,且日内区间已算好(isHaveDailyRange_Prices)、尚未触发过突破(!isHaveRangeBreak),并且 barTime 落在 validBreakTime_start 到 validBreakTime_end 的合法窗口内。命中后打印日志、置位 isHaveRangeBreak,并调用 drawBreakPoint 以编号 234、黑色在突破位画点。 下单用 obj_Trade.Buy(0.01, _Symbol, Ask, minimum_price, Bid+(maximum_price-minimum_price)*2):0.01 手试单,止损放在区间下沿 minimum_price,止盈距离为区间高度的两倍——这种倍数止盈在震荡转趋势的行情里可能比固定点数更跟得住。 下破对称处理:barClose 小于 minimum_price 且其他标志相同,画点编号 233、蓝色,obj_Trade.Sell 的止损设于 maximum_price,止盈为 Ask-(maximum_price-minimum_price)*2。外汇与贵金属杠杆高,这类突破追单在假突破频发的时段可能连续止损,建议先在策略测试器用真实点差回测再上仿真。
class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">//--- Check for upper range breakout condition if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak && barTime >= validBreakTime_start && barTime <= validBreakTime_end){ Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); class=class="str">"cmt">//--- Log the breakout event isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">234,clrBlack,-class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout obj_Trade.Buy(class="num">0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*class="num">2); } class=class="str">"cmt">//--- Check for lower range breakout condition else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak && barTime >= validBreakTime_start && barTime <= validBreakTime_end){ Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); class=class="str">"cmt">//--- Log the breakout event isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">233,clrBlue,class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout obj_Trade.Sell(class="num">0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*class="num">2); }
「用策略测试器榨出参数组合」
EA 写完后别急着上实盘,先丢进 MT5 策略测试器跑回测。日波动区间突破这种依赖时间窗口和盈亏比的策略,参数稍微一动,信号触发频率就能差出几倍,外汇和贵金属市场的高波动特性会放大这种偏差,必须先在历史数据里压一遍。 优化时把风险回报比、突破有效时长(小时)、交易方向三个量做成 input 暴露出来。原文示例里 r2r 默认 2、hoursValidity 默认 5,direction_of_trade 用枚举控制默认或反转方向——下边界被破时默认做空,切到 Invert 就改做多,核心逻辑不用动。 避免过度拟合有个土办法:优化只框一个月。跑完看参数曲面,挑曲线平坦那段而不是峰值最高的那组,实盘存活概率更高。 下面这段是参数声明加日内时间窗重置的核心。枚举让方向切换可读,input 让测试器能逐格扫;hoursValidity 被黄标高亮,因为它直接决定 validBreakTime_end 往后推几小时。
enum trade_direction {Default_Trade_Directions,Invert_Trade_Directions}; class="kw">input class="type">int r2r = class="num">2; class="kw">input class="type">int hoursValidity = class="num">5; class="kw">input trade_direction direction_of_trade = Default_Trade_Directions; class="kw">static class="type">class="kw">datetime midnight = iTime(_Symbol,PERIOD_D1,class="num">0); class=class="str">"cmt">//--- Get the time of midnight(start of the day) for daily chart class="kw">static class="type">class="kw">datetime sixAM = midnight + class="num">6 * class="num">3600; class=class="str">"cmt">//--- Calculate class="num">6 AM based on midnight time class="kw">static class="type">class="kw">datetime scanBarTime = sixAM + class="num">1 * PeriodSeconds(_Period); class=class="str">"cmt">//--- Set scan time for the next bar after class="num">6 AM class="kw">static class="type">class="kw">datetime validBreakTime_start = scanBarTime; class=class="str">"cmt">//--- Set the start of valid breakout time class="kw">static class="type">class="kw">datetime validBreakTime_end = midnight + (class="num">6+hoursValidity) * class="num">3600; class=class="str">"cmt">//--- Set the end of valid breakout time to class="num">11 AM if (isNewDay()){ class=class="str">"cmt">//--- Reset values for the new day midnight = iTime(_Symbol,PERIOD_D1,class="num">0); class=class="str">"cmt">//--- Get the new midnight time sixAM = midnight + class="num">6 * class="num">3600; class=class="str">"cmt">//--- Recalculate class="num">6 AM scanBarTime = sixAM + class="num">1 * PeriodSeconds(_Period); class=class="str">"cmt">//--- Recalculate the scan bar time validBreakTime_start = scanBarTime; class=class="str">"cmt">//--- Update valid breakout start time validBreakTime_end = midnight + (class="num">6+hoursValidity) * class="num">3600; class=class="str">"cmt">//--- Update valid breakout end time to class="num">11 AM maximum_price = -DBL_MAX; class=class="str">"cmt">//--- Reset the maximum price for the new day minimum_price = DBL_MAX; class=class="str">"cmt">//--- Reset the minimum price for the new day isHaveDailyRange_Prices = false; class=class="str">"cmt">//--- Reset the daily range flag for the new day isHaveRangeBreak = false; class=class="str">"cmt">//--- Reset the breakout flag for the new day } class=class="str">"cmt">//--- class=class="str">"cmt">//--- Check for upper range breakout condition if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
◍ 收盘价突破日区间后的下单分支
这段逻辑只在 bars 收盘触破当日统计出的 high/low 区间、且处于 validBreakTime 窗口内、且尚未标记过突破时才触发。突破上沿时打印日志、置 isHaveRangeBreak 为真,并用 drawBreakPoint 在 234 号箭头(黑)标出突破点;突破下沿则用 233 号箭头(蓝)标记。 交易动作取决于 direction_of_trade 枚举:默认方向下,上破做多 0.01 手,止损放在 minimum_price,止盈为 Bid + (maximum_price-minimum_price)*r2r;下破做空,止损放 maximum_price,止盈为 Ask - 区间幅度*r2r。若切到 Invert_Trade_Directions,上下破信号反手执行。 代码头部可见 r2r 输入默认 2、hoursValidity 默认 5,意味着风险回报比预设 1:2、突破有效窗口为 5 小时。外汇与贵金属杠杆品种波动剧烈,实盘前应在 MT5 策略测试器用 0.01 手验证该止损止盈距离是否被平台最小止损规则拒绝。
&& barTime >= validBreakTime_start && barTime <= validBreakTime_end){
Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); class=class="str">"cmt">//--- Log the breakout event
isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred
drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">234,clrBlack,-class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout
if (direction_of_trade == Default_Trade_Directions){
obj_Trade.Buy(class="num">0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*r2r);
}
else if (direction_of_trade == Invert_Trade_Directions){
obj_Trade.Sell(class="num">0.01,_Symbol,Bid,Ask+(maximum_price-minimum_price),Ask-(maximum_price-minimum_price)*r2r);
}
}
class=class="str">"cmt">//--- Check for lower range breakout condition
else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
&& barTime >= validBreakTime_start && barTime <= validBreakTime_end){
Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); class=class="str">"cmt">//--- Log the breakout event
isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred
drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">233,clrBlue,class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout
if (direction_of_trade == Default_Trade_Directions){
obj_Trade.Sell(class="num">0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*r2r);
}
else if (direction_of_trade == Invert_Trade_Directions){
obj_Trade.Buy(class="num">0.01,_Symbol,Ask,Bid-(maximum_price-minimum_price),Bid+(maximum_price-minimum_price)*r2r);
}
}用静态时间锚点框定日内区间扫描窗
做日内突破策略时,先得把“当天该扫哪段”钉死。下面这段初始化把午夜、早6点、扫描 bar 时间以及有效突破窗口都用 static 变量算好,避免每个 tick 重复计算,也防止跨日错乱。 double minimum_price = DBL_MAX 是把最低价初值推到浮点上限,保证第一根符合条件的 K 线必能刷新它;maximum_time / minimum_time 留着存高低点发生时刻;isHaveDailyRange_Prices 与 isHaveRangeBreak 是两个状态锁,防止一天内重复画矩形或重复触发突破信号。 宏定义给图形对象加前缀:RECTANGLE_PREFIX 为 "RANGE RECTANGLE ",上下边界线分别是 "UPPER LINE " 和 "LOWER LINE "。这样在 MT5 对象列表里能一眼区分不同用途的画线,不会和手动画的趋势线混在一起。 OnInit / OnDeinit 这里留空,仅返回 INIT_SUCCEEDED,说明本段只负责时间锚点与状态变量的声明,具体抓取逻辑在 OnTick 内按日重置。注意 validBreakTime_end 用 midnight + (6+hoursValidity)*3600 计算,若 hoursValidity=5 则窗口收在 11:00(6+5=11),外汇与贵金属此时流动性变化明显,假突破概率可能上升,需结合更高周期过滤。 打开 MT5 新建 EA,把这段直接贴进文件,改 hoursValidity 参数到 4~6 之间,编译后挂 EURUSD 的 M5,观察对象列表是否按前缀生成矩形,即可验证锚点逻辑是否成立。
class="type">class="kw">double minimum_price = DBL_MAX; class=class="str">"cmt">//--- Initialize the minimum price with the largest possible value class="type">class="kw">datetime maximum_time, minimum_time; class=class="str">"cmt">//--- Declare variables to store the time of the highest and lowest prices class="type">bool isHaveDailyRange_Prices = false; class=class="str">"cmt">//--- Boolean flag to check if daily range prices are extracted class="type">bool isHaveRangeBreak = false; class=class="str">"cmt">//--- Boolean flag to check if a range breakout has occurred class="macro">#define RECTANGLE_PREFIX "RANGE RECTANGLE " class=class="str">"cmt">//--- Prefix for naming range rectangles class="macro">#define UPPER_LINE_PREFIX "UPPER LINE " class=class="str">"cmt">//--- Prefix for naming upper range line class="macro">#define LOWER_LINE_PREFIX "LOWER LINE " class=class="str">"cmt">//--- Prefix for naming lower range line class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit(){ class=class="str">"cmt">//--- Initialization code can be placed here if needed class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">//--- Return successful initialization } 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">//--- Deinitialization code can be placed here if needed } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick(){ class=class="str">"cmt">//--- class="kw">static class="type">class="kw">datetime midnight = iTime(_Symbol,PERIOD_D1,class="num">0); class=class="str">"cmt">//--- Get the time of midnight(start of the day) for daily chart class="kw">static class="type">class="kw">datetime sixAM = midnight + class="num">6 * class="num">3600; class=class="str">"cmt">//--- Calculate class="num">6 AM based on midnight time class="kw">static class="type">class="kw">datetime scanBarTime = sixAM + class="num">1 * PeriodSeconds(_Period); class=class="str">"cmt">//--- Set scan time for the next bar after class="num">6 AM class="kw">static class="type">class="kw">datetime validBreakTime_start = scanBarTime; class=class="str">"cmt">//--- Set the start of valid breakout time class="kw">static class="type">class="kw">datetime validBreakTime_end = midnight + (class="num">6+hoursValidity) * class="num">3600; class=class="str">"cmt">//--- Set the end of valid breakout time to class="num">11 AM if (isNewDay()){ class=class="str">"cmt">//--- Reset values for the new day midnight = iTime(_Symbol,PERIOD_D1,class="num">0); class=class="str">"cmt">//--- Get the new midnight time sixAM = midnight + class="num">6 * class="num">3600; class=class="str">"cmt">//--- Recalculate class="num">6 AM scanBarTime = sixAM + class="num">1 * PeriodSeconds(_Period); class=class="str">"cmt">//--- Recalculate the scan bar time
「日内区间扫描与突破窗口的初始化逻辑」
这段逻辑处理的是每日凌晨重置与 6 点区间提取的衔接。新的一天到来时,代码先把有效突破的起止时间、当日最高/最低价、以及两个状态标志(是否已提取区间、是否已发生突破)全部清零,保证上一日数据不会污染当日计算。 其中有效突破结束时间被硬性设为 midnight + (6 + hoursValidity) * 3600,也就是在凌晨 6 点基础上再加 hoursValidity 小时,若 hoursValidity 取 5,则窗口关闭于当天 11:00(夏令时服务器时间)。这是后续判断「亚洲段区间是否被有效破位」的时间边界。 当 isNewBar() 触发且当前 Bar 时间等于 scanBarTime、且当日区间尚未提取时,脚本会先算出 midnight 到 6:00 之间的 Bar 数量:total_bars = (sixAM - midnight) / PeriodSeconds(_Period) + 1。以 M5 周期为例,6 小时共 72 根 Bar,total_bars 即为 73(含起点那根)。 随后用 for 循环从 i=1 遍历到 total_bars,对每根 Bar 取 open 与 close,以两者中较大值为该 Bar 的「最高参考价」、较小值为「最低参考价」,再据此刷新全局 maximum_price / minimum_price 及对应索引和时间。注意这里没有用影线高低,只用实体边界,对外汇和贵金属这类高风险品种,实体突破比影线刺穿更不容易出现假信号。 打开 MT5 把这段接进 EA,把 hoursValidity 改成你常用的持有时长,跑一周模拟盘就能验证 11:00 前未破区间是否被准确标注。
validBreakTime_start = scanBarTime; class=class="str">"cmt">//--- Update valid breakout start time validBreakTime_end = midnight + (class="num">6+hoursValidity) * class="num">3600; class=class="str">"cmt">//--- Update valid breakout end time to class="num">11 AM maximum_price = -DBL_MAX; class=class="str">"cmt">//--- Reset the maximum price for the new day minimum_price = DBL_MAX; class=class="str">"cmt">//--- Reset the minimum price for the new day isHaveDailyRange_Prices = false; class=class="str">"cmt">//--- Reset the daily range flag for the new day isHaveRangeBreak = false; class=class="str">"cmt">//--- Reset the breakout flag for the new day } if (isNewBar()){ class=class="str">"cmt">//--- If a new bar has been formed, process the data class="type">class="kw">datetime currentBarTime = iTime(_Symbol,_Period,class="num">0); class=class="str">"cmt">//--- Get the time of the current bar if (currentBarTime == scanBarTime && !isHaveDailyRange_Prices){ class=class="str">"cmt">//--- If it&class="macro">#x27;s time to scan and the daily range is not yet extracted Print("WE HAVE ENOUGH BARS DATA FOR DOCUMENTATION. MAKE THE EXTRACTION"); class=class="str">"cmt">//--- Log the extraction process class="type">int total_bars = class="type">int((sixAM - midnight)/PeriodSeconds(_Period)) + class="num">1; class=class="str">"cmt">//--- Calculate total bars between midnight and class="num">6 AM Print("Total Bars for scan = ",total_bars); class=class="str">"cmt">//--- Log the total number of bars for scanning class="type">int highest_price_bar_index = -class="num">1; class=class="str">"cmt">//--- Variable to store the bar index of the highest price class="type">int lowest_price_bar_index = -class="num">1; class=class="str">"cmt">//--- Variable to store the bar index of the lowest price for (class="type">int i=class="num">1; i<=total_bars ; i++){ class=class="str">"cmt">//--- Loop through all bars within the defined time range class="type">class="kw">double open_i = open(i); class=class="str">"cmt">//--- Get the opening price of the i-th bar class="type">class="kw">double close_i = close(i); class=class="str">"cmt">//--- Get the closing price of the i-th bar class="type">class="kw">double highest_price_i = (open_i > close_i) ? open_i : close_i; class=class="str">"cmt">//--- Determine the highest price between open and close class="type">class="kw">double lowest_price_i = (open_i < close_i) ? open_i : close_i; class=class="str">"cmt">//--- Determine the lowest price between open and close if (highest_price_i > maximum_price){ class=class="str">"cmt">//--- If the current highest price is greater than the recorded maximum price maximum_price = highest_price_i; class=class="str">"cmt">//--- Update the maximum price highest_price_bar_index = i; class=class="str">"cmt">//--- Update the index of the highest price bar maximum_time = time(i); class=class="str">"cmt">//--- Update the time of the highest price
◍ 日内区间突破的触发与下单逻辑
这段代码片段负责在已提取日内最高/最低价后,监听上一根_bar的收盘价是否向上击穿区间上沿。只有当 isHaveDailyRange_Prices 为真、且未发生过突破、并且_bar时间落在 validBreakTime_start 到 validBreakTime_end 之间时,才会判定为有效突破。外汇与贵金属杠杆高,假突破概率不低,实盘前务必在MT5策略测试器里跑一遍。
核心判定写得很直白:barClose > maximum_price 即收盘价高于日内最高价,同时用 !isHaveRangeBreak 防止重复触发。一旦成立,先 Print 日志、置 isHaveRangeBreak=true,再调用 drawBreakPoint 在图表上标出突破点(箭头代码234、黑色)。
下单分支取决于 direction_of_trade:若为默认方向,则以0.01手买入,止损设在 minimum_price(区间下沿),止盈为 Bid+(maximum_price-minimum_price)*r2r——即跌破区间幅度乘以r2r系数。若设为反向交易,则走 Invert_Trade_Directions 分支(原文未展开)。
把 r2r 从默认改到2.0或0.5,回测里能明显看到胜率与平均盈利的分布偏移,建议先调这一个参数验证区间突破策略的脆弱点。
if (lowest_price_i < minimum_price){ class=class="str">"cmt">//--- If the current lowest price is lower than the recorded minimum price minimum_price = lowest_price_i; class=class="str">"cmt">//--- Update the minimum price lowest_price_bar_index = i; class=class="str">"cmt">//--- Update the index of the lowest price bar minimum_time = time(i); class=class="str">"cmt">//--- Update the time of the lowest price } } class=class="str">"cmt">//--- Log the maximum and minimum prices, along with their respective bar indices and times Print("Maximum Price = ",maximum_price,", Bar index = ",highest_price_bar_index,", Time = ",maximum_time); Print("Minimum Price = ",minimum_price,", Bar index = ",lowest_price_bar_index,", Time = ",minimum_time); class=class="str">"cmt">//--- Create visual elements to represent the daily range create_Rectangle(RECTANGLE_PREFIX+TimeToString(maximum_time),maximum_time,maximum_price,minimum_time,minimum_price,clrBlue); class=class="str">"cmt">//--- Create a rectangle for the daily range create_Line(UPPER_LINE_PREFIX+TimeToString(midnight),midnight,maximum_price,sixAM,maximum_price,class="num">3,clrBlack,DoubleToString(maximum_price,_Digits)); class=class="str">"cmt">//--- Draw upper range line create_Line(LOWER_LINE_PREFIX+TimeToString(midnight),midnight,minimum_price,sixAM,minimum_price,class="num">3,clrRed,DoubleToString(minimum_price,_Digits)); class=class="str">"cmt">//--- Draw lower range line isHaveDailyRange_Prices = true; class=class="str">"cmt">//--- Set the flag indicating daily range prices have been extracted } } class=class="str">"cmt">//--- Get the close price and time of the previous bar class="type">class="kw">double barClose = close(class="num">1); class="type">class="kw">datetime barTime = time(class="num">1); class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">//--- Check for upper range breakout condition if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak && barTime >= validBreakTime_start && barTime <= validBreakTime_end){ Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); class=class="str">"cmt">//--- Log the breakout event isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">234,clrBlack,-class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout if (direction_of_trade == Default_Trade_Directions){ obj_Trade.Buy(class="num">0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*r2r); } else if (direction_of_trade == Invert_Trade_Directions){
下破区间时的反向挂单与辅助函数
当收盘价低于 minimum_price 且处于有效突破时间窗内,系统判定为下轨突破,置位 isHaveRangeBreak 并调用 drawBreakPoint 在蓝点标记。若交易方向设为默认,则直接 Sell 0.01 手,止损放在 maximum_price,止盈为 Ask 减去区间高度乘以 r2r 系数;若设为反向,则转为 Buy 0.01 手,止损与止盈对称翻转。 下方五个小工具函数把 MT5 内置序列访问封装成 open/high/low/close/time,入参为柱索引,返回对应品种与周期的数据,写策略时直接 open(i) 比反复调 iOpen(_Symbol,_Period,i) 更干净。 create_Rectangle 负责在图表画区间框:先用 ObjectFind 判重,不存在才 ObjectCreate 生成 OBJ_RECTANGLE,随后用 ObjectSetInteger 写第一点时间坐标。外汇与贵金属波动剧烈,这类突破单触发后滑点可能吞掉预设止损,实盘前务必在 MT5 策略测试器用历史数据核对 r2r 与 validBreakTime 的联合表现。
obj_Trade.Sell(class="num">0.01,_Symbol,Bid,Ask+(maximum_price-minimum_price),Ask-(maximum_price-minimum_price)*r2r); } } class=class="str">"cmt">//--- Check for lower range breakout condition else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak && barTime >= validBreakTime_start && barTime <= validBreakTime_end){ Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); class=class="str">"cmt">//--- Log the breakout event isHaveRangeBreak = true; class=class="str">"cmt">//--- Set the flag indicating a breakout occurred drawBreakPoint(TimeToString(barTime),barTime,barClose,class="num">233,clrBlue,class="num">1); class=class="str">"cmt">//--- Draw a point to mark the breakout if (direction_of_trade == Default_Trade_Directions){ obj_Trade.Sell(class="num">0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*r2r); } else if (direction_of_trade == Invert_Trade_Directions){ obj_Trade.Buy(class="num">0.01,_Symbol,Ask,Bid-(maximum_price-minimum_price),Bid+(maximum_price-minimum_price)*r2r); } } } class=class="str">"cmt">//--- Utility functions to retrieve price and time data for a given bar index class="type">class="kw">double open(class="type">int index){class="kw">return (iOpen(_Symbol,_Period,index));} class=class="str">"cmt">//--- Get the opening price class="type">class="kw">double high(class="type">int index){class="kw">return (iHigh(_Symbol,_Period,index));} class=class="str">"cmt">//--- Get the highest price class="type">class="kw">double low(class="type">int index){class="kw">return (iLow(_Symbol,_Period,index));} class=class="str">"cmt">//--- Get the lowest price class="type">class="kw">double close(class="type">int index){class="kw">return (iClose(_Symbol,_Period,index));} class=class="str">"cmt">//--- Get the closing price class="type">class="kw">datetime time(class="type">int index){class="kw">return (iTime(_Symbol,_Period,index));} class=class="str">"cmt">//--- Get the time of the bar class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| FUNCTION TO CREATE A RECTANGLE | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void create_Rectangle(class="type">class="kw">string objName, class="type">class="kw">datetime time1, class="type">class="kw">double price1, class="type">class="kw">datetime time2, class="type">class="kw">double price2, class="type">class="kw">color clr) { class=class="str">"cmt">//--- Check if the object already exists by finding it on the chart if (ObjectFind(class="num">0, objName) < class="num">0) { class=class="str">"cmt">//--- Create a rectangle object using the defined parameters: name, type, and coordinates ObjectCreate(class="num">0, objName, OBJ_RECTANGLE, class="num">0, time1, price1, time2, price2); class=class="str">"cmt">//--- Set the time for the first point of the rectangle(start point) ObjectSetInteger(class="num">0, objName, OBJPROP_TIME, class="num">0, time1); class=class="str">"cmt">//--- Set the price for the first point of the rectangle(start point)
「矩形与趋势线的属性落地写法」
在 MT5 里用代码画矩形,第二点的时间与价格必须分别用 ObjectSetInteger 和 ObjectSetDouble 写到位,索引参数填 1 而不是 0。矩形是否填充、颜色、是否置底,全靠 OBJPROP_FILL / OBJPROP_COLOR / OBJPROP_BACK 三个整型属性控制,最后不调 ChartRedraw(0) 图表不会即时刷新。 趋势线函数 create_Line 先以 ObjectFind 判重,返回负值才 ObjectCreate,类型用 OBJ_TREND,同样把两个点的时间、价格按 0 和 1 索引设好。线宽走 OBJPROP_WIDTH、颜色走 OBJPROP_COLOR,OBJPROP_BACK 设 false 可保证线压在其他对象之上。 实盘加载这类脚本时注意:外汇与贵金属杠杆高、滑点大,对象坐标若用历史精确价位,换周期后可能偏移,建议先在 EURUSD 的 M5 上跑一遍验证锚点。
ObjectSetDouble(class="num">0, objName, OBJPROP_PRICE, class="num">0, price1); class=class="str">"cmt">//--- Set the time for the second point of the rectangle(end point) ObjectSetInteger(class="num">0, objName, OBJPROP_TIME, class="num">1, time2); class=class="str">"cmt">//--- Set the price for the second point of the rectangle(end point) ObjectSetDouble(class="num">0, objName, OBJPROP_PRICE, class="num">1, price2); class=class="str">"cmt">//--- Enable the fill class="kw">property for the rectangle, making it filled ObjectSetInteger(class="num">0, objName, OBJPROP_FILL, true); class=class="str">"cmt">//--- Set the class="type">class="kw">color for the rectangle ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clr); class=class="str">"cmt">//--- Set the rectangle to not appear behind other objects ObjectSetInteger(class="num">0, objName, OBJPROP_BACK, false); class=class="str">"cmt">//--- Redraw the chart to reflect the new changes ChartRedraw(class="num">0); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| FUNCTION TO CREATE A TREND LINE | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void create_Line(class="type">class="kw">string objName, class="type">class="kw">datetime time1, class="type">class="kw">double price1, class="type">class="kw">datetime time2, class="type">class="kw">double price2, class="type">int width, class="type">class="kw">color clr, class="type">class="kw">string text) { class=class="str">"cmt">//--- Check if the line object already exists by its name if (ObjectFind(class="num">0, objName) < class="num">0) { class=class="str">"cmt">//--- Create a trendline object with the specified parameters ObjectCreate(class="num">0, objName, OBJ_TREND, class="num">0, time1, price1, time2, price2); class=class="str">"cmt">//--- Set the time for the first point of the trendline ObjectSetInteger(class="num">0, objName, OBJPROP_TIME, class="num">0, time1); class=class="str">"cmt">//--- Set the price for the first point of the trendline ObjectSetDouble(class="num">0, objName, OBJPROP_PRICE, class="num">0, price1); class=class="str">"cmt">//--- Set the time for the second point of the trendline ObjectSetInteger(class="num">0, objName, OBJPROP_TIME, class="num">1, time2); class=class="str">"cmt">//--- Set the price for the second point of the trendline ObjectSetDouble(class="num">0, objName, OBJPROP_PRICE, class="num">1, price2); class=class="str">"cmt">//--- Set the width for the line ObjectSetInteger(class="num">0, objName, OBJPROP_WIDTH, width); class=class="str">"cmt">//--- Set the class="type">class="kw">color of the trendline ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clr); class=class="str">"cmt">//--- Set the trendline to not be behind other objects
◍ 依图表缩放等级自适应标注字号
在 MT5 里画水平线或对象时,若固定字号,缩放拉到最大(scale=5)会挤成一团,缩到最小(scale=0)又小到看不清。上面这段逻辑先通过 ChartGetInteger 取当前 CHART_SCALE,失败则在日志提示并沿用默认 0。 随后用 if-else 把缩放等级映射到字号:scale 0→5、1→6、2→7、3→9、4→11、5→13。这样无论你怎么滚轮缩放,右侧价位的文字标注都保持可读。外汇与贵金属波动剧烈,图表缩放是高频操作,自适应能少掉很多视觉干扰。 取回 scale 后,代码在 price2 处用 OBJ_TEXT 建一个文本对象,锚点设 ANCHOR_LEFT 贴着线左,颜色继承 clr,字体写死 Calibri,内容由外部 text 传入,最后 ChartRedraw 刷新。 isNewBar 函数则靠静态变量 prevBars 存上一帧 BAR 数,iBars 取当前数,相等即非新 BAR 返回 false,这是控制标注只在新 K 线重画的常用闸门。
ObjectSetInteger(class="num">0, objName, OBJPROP_BACK, false); class=class="str">"cmt">//--- Retrieve the current chart scale class="type">long scale = class="num">0; if(!ChartGetInteger(class="num">0, CHART_SCALE, class="num">0, scale)) { class=class="str">"cmt">//--- Print an error message if unable to retrieve the chart scale Print("UNABLE TO GET THE CHART SCALE. DEFAULT OF ", scale, " IS CONSIDERED"); } class=class="str">"cmt">//--- Set a class="kw">default font size based on the chart scale class="type">int fontsize = class="num">11; if (scale == class="num">0) { fontsize = class="num">5; } else if (scale == class="num">1) { fontsize = class="num">6; } else if (scale == class="num">2) { fontsize = class="num">7; } else if (scale == class="num">3) { fontsize = class="num">9; } else if (scale == class="num">4) { fontsize = class="num">11; } else if (scale == class="num">5) { fontsize = class="num">13; } class=class="str">"cmt">//--- Define the description text to appear near the right price class="type">class="kw">string txt = " Right Price"; class="type">class="kw">string objNameDescr = objName + txt; class=class="str">"cmt">//--- Create a text object next to the line to display the description ObjectCreate(class="num">0, objNameDescr, OBJ_TEXT, class="num">0, time2, price2); class=class="str">"cmt">//--- Set the class="type">class="kw">color for the text ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_COLOR, clr); class=class="str">"cmt">//--- Set the font size for the text ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_FONTSIZE, fontsize); class=class="str">"cmt">//--- Anchor the text to the left of the line ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT); class=class="str">"cmt">//--- Set the text content to display the specified class="type">class="kw">string ObjectSetString(class="num">0, objNameDescr, OBJPROP_TEXT, " " + text); class=class="str">"cmt">//--- Set the font of the text to "Calibri" ObjectSetString(class="num">0, objNameDescr, OBJPROP_FONT, "Calibri"); class=class="str">"cmt">//--- Redraw the chart to reflect the changes ChartRedraw(class="num">0); } } class="type">bool isNewBar() { class=class="str">"cmt">//--- Static variable to hold the previous number of bars class="kw">static class="type">int prevBars = class="num">0; class=class="str">"cmt">//--- Get the current number of bars on the chart class="type">int currBars = iBars(_Symbol, _Period); class=class="str">"cmt">//--- If the number of bars hasn&class="macro">#x27;t changed, class="kw">return false if (prevBars == currBars) class="kw">return (false);
日界判定与箭头落点怎么写
日内策略最怕把跨日波动误判成同根 K 线延续。下面这段逻辑用静态变量 prevDay 存前一天序号,每次调 isNewDay() 时把 TimeCurrent() 拆成 MqlDateTime 结构取 day 字段,不相等就置 newDay=true 并 Print 出日期,相等则保持 false。 静态变量只初始化一次,所以 prevDay 在 EA 重启前一直保留,避免每 tick 重算。外汇和贵金属隔夜跳空概率不低,用这个函数切日线上下文,比直接比日期字符串省事。 drawBreakPoint() 负责把突破点画成箭头:先 ObjectFind 看对象在不在,不在就 ObjectCreate 挂 OBJ_ARROW,再设箭头代码、颜色、12 号字体。direction>0 锚定顶部、<0 锚定底部,这样上破和下破的箭头不会和蜡烛重叠。 开 MT5 把这几个函数塞进脚本,日线切换时终端会打印『WE HAVE A NEW DAY WITH DATE 数字』,箭头也会随方向自动贴边,肉眼能直接验证锚点逻辑对不对。
class="type">bool isNewDay() { class="type">bool newDay = false; class="type">MqlDateTime Str_DateTime; TimeToStruct(TimeCurrent(), Str_DateTime); class="kw">static class="type">int prevDay = class="num">0; class="type">int currDay = Str_DateTime.day; if (prevDay == currDay) { newDay = false; } else if (prevDay != currDay) { Print("WE HAVE A NEW DAY WITH DATE ", currDay); prevDay = currDay; newDay = true; } class="kw">return (newDay); } class="type">void drawBreakPoint(class="type">class="kw">string objName, class="type">class="kw">datetime time, class="type">class="kw">double price, class="type">int arrCode, class="type">class="kw">color clr, class="type">int direction) { if (ObjectFind(class="num">0, objName) < class="num">0) { ObjectCreate(class="num">0, objName, OBJ_ARROW, class="num">0, time, price); ObjectSetInteger(class="num">0, objName, OBJPROP_ARROWCODE, arrCode); ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clr); ObjectSetInteger(class="num">0, objName, OBJPROP_FONTSIZE, class="num">12); if (direction > class="num">0) ObjectSetInteger(class="num">0, objName, OBJPROP_ANCHOR, ANCHOR_TOP); if (direction < class="num">0) ObjectSetInteger(class="num">0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM); } }
「给突破点加带方向的文字标注」
在 MT5 的 EA 或指标里给图形对象补文字,最常见的是给箭头旁边写一句「Break」说明。下面这段逻辑就是按多空方向把文字锚点甩到不同角落,避免压住 K 线。 核心做法是先拼出对象名,再用 ObjectCreate 以 OBJ_TEXT 类型在指定 time、price 上落字;颜色与字号分别用 OBJPROP_COLOR、OBJPROP_FONTSIZE 写死,示例里字号取 12,肉眼在 1920×1080 屏上约占用 3~4 根 1 分钟 K 线的高度。 方向判断决定锚点:direction>0 时挂 ANCHOR_LEFT_UPPER,文字在箭头左上;direction<0 时换 ANCHOR_LEFT_LOWER,掉到左下。最后 ChartRedraw(0) 强制重绘,不然新对象可能要等下一次 tick 才显出来。外汇与贵金属波动快,这类标注仅作视觉辅助,信号失效概率不低,实盘须自担高风险。
class="type">class="kw">string txt = " Break"; class="type">class="kw">string objNameDescr = objName + txt; class=class="str">"cmt">//--- Create a text object for the break point description ObjectCreate(class="num">0, objNameDescr, OBJ_TEXT, class="num">0, time, price); class=class="str">"cmt">//--- Set the class="type">class="kw">color for the text description ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_COLOR, clr); class=class="str">"cmt">//--- Set the font size for the text ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_FONTSIZE, class="num">12); class=class="str">"cmt">//--- Adjust the text anchor based on the direction of the arrow if (direction > class="num">0) { ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); ObjectSetString(class="num">0, objNameDescr, OBJPROP_TEXT, " " + txt); } if (direction < class="num">0) { ObjectSetInteger(class="num">0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); ObjectSetString(class="num">0, objNameDescr, OBJPROP_TEXT, " " + txt); } } class=class="str">"cmt">//--- Redraw the chart to reflect the new objects ChartRedraw(class="num">0); }
◍ 把这条线请下神坛
这套每日区间突破 EA 的核心其实就三件事:用 MQL5 算出自午夜到早 6 点的当日区间、在图表上画矩形和趋势线标出突破位、突破瞬间由监控函数抓价差发单。作者在策略测试器里跑过,但没给任何收益率数字,只说要按自己的交易条件调参。 评论区有人指出原文标题有误导,实际更像“晨间平盘突破”,且代码没处理服务器 GMT 偏移,若你的券商午夜不是 00:00 开盘,就得自己改 TimeCurrent 的时区对齐逻辑。 外汇和贵金属杠杆高,自动化策略随时可能因滑点或流动性断层失效,拿去真金白银跑之前务必在 MT5 测试器用至少三年 tick 数据回测。 把 EA 文件里的 Input 参数公开出来,先只调 RangeStartHour 和 RangeEndHour 两个值,看不同币种对突破频率的影响,比直接信“突破必动”实在得多。