MQL5交易策略自动化(第十六部分):基于结构突破(BoS)价格行为的午夜区间突破策略·综合运用
「在摆动点挂文字标签的画法」
MT5 里给价格摆动点做标注,核心是用 OBJ_TEXT 对象把文字钉在指定的 time/price 坐标上。下面这段逻辑会在摆动低点把文字锚定在左上、摆动高点锚定在左下,避免标签压住 K 线实体。 ObjectCreate(0,objName_Descr,OBJ_TEXT,0,time,price) 建对象,0 代表当前图表;OBJ_TEXT 类型决定它是纯文本。随后 ObjectSetInteger 设颜色与字号 10,字号偏小适合多摆动点同屏不挤。direction>0 判为低点用 ANCHOR_LEFT_UPPER,direction<0 判为高点用 ANCHOR_LEFT_LOWER,最后 ChartRedraw(0) 强制重绘才看得见。 BoS 逻辑里先用 iBars 比对新旧柱数判断新柱:prevBars!=currBars 才置 isNewBar_bos=true,这是防止每 tick 重算摆动的基础。摆动判定取 length=4,即当前柱前后各比 4 根,curr_bar 从 4 起步保证左右都有样本;isSwingHigh / isSwingLow 初始双 true,循环里用 right_index=curr_bar-a 与 left_index=curr_bar+a 做对称比较。外汇与贵金属波动跳空多,这类静态 length 在重大数据周可能漏掉宽幅摆动,实盘前建议在 MT5 策略测试器用 EURUSD 日线先跑一遍看标错率。
ObjectCreate(class="num">0,objName_Descr,OBJ_TEXT,class="num">0,time,price); class=class="str">"cmt">//--- Create a text object at the swing point ObjectSetInteger(class="num">0,objName_Descr,OBJPROP_COLOR,clr); class=class="str">"cmt">//--- Set the class="type">color of the text ObjectSetInteger(class="num">0,objName_Descr,OBJPROP_FONTSIZE,class="num">10); class=class="str">"cmt">//--- Set the font size of the text if (direction > class="num">0) { class=class="str">"cmt">//--- Check if the swing is a low ObjectSetString(class="num">0,objName_Descr,OBJPROP_TEXT," "+text); class=class="str">"cmt">//--- Set the text content ObjectSetInteger(class="num">0,objName_Descr,OBJPROP_ANCHOR,ANCHOR_LEFT_UPPER); class=class="str">"cmt">//--- Set the text anchor to left upper } if (direction < class="num">0) { class=class="str">"cmt">//--- Check if the swing is a high ObjectSetString(class="num">0,objName_Descr,OBJPROP_TEXT," "+text); class=class="str">"cmt">//--- Set the text content ObjectSetInteger(class="num">0,objName_Descr,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); class=class="str">"cmt">//--- Set the text anchor to left lower } } ChartRedraw(class="num">0); class=class="str">"cmt">//--- Redraw the chart to display the swing point } class=class="str">"cmt">// bos logic if (isHaveDailyRange_Prices){ class=class="str">"cmt">//--- Proceed with BoS logic only if the daily range is calculated class="kw">static class="type">bool isNewBar_bos = class="kw">false; class=class="str">"cmt">//--- Initialize flag to indicate a new bar on the BoS timeframe class="type">int currBars = iBars(_Symbol,timeframe_bos); class=class="str">"cmt">//--- Get the current number of bars on the BoS timeframe class="kw">static class="type">int prevBars = currBars; class=class="str">"cmt">//--- Store the previous number of bars for comparison if (prevBars == currBars){isNewBar_bos = class="kw">false;} class=class="str">"cmt">//--- Set flag to class="kw">false if no new bar has formed else if (prevBars != currBars){isNewBar_bos = true; prevBars = currBars;} class=class="str">"cmt">//--- Set flag to true and update prevBars if a new bar has formed const class="type">int length = class="num">4; class=class="str">"cmt">//--- Define the number of bars to check for swing high/low(must be > class="num">2) class="type">int right_index, left_index; class=class="str">"cmt">//--- Declare variables to store indices for bars to the right and left class="type">int curr_bar = length; class=class="str">"cmt">//--- Set the current bar index for swing analysis class="type">bool isSwingHigh = true, isSwingLow = true; class=class="str">"cmt">//--- Initialize flags to determine if the current bar is a swing high or low class="kw">static class="type">class="kw">double swing_H = -class="num">1.0, swing_L = -class="num">1.0; class=class="str">"cmt">//--- Initialize variables to store the latest swing high and low prices if (isNewBar_bos){ class=class="str">"cmt">//--- Check if a new bar has formed on the BoS timeframe for (class="type">int a=class="num">1; a<=length; a++){ class=class="str">"cmt">//--- Loop through the specified number of bars to check for swings right_index = curr_bar - a; class=class="str">"cmt">//--- Calculate the right-side bar index left_index = curr_bar + a; class=class="str">"cmt">//--- Calculate the left-side bar index
摆动点判定与破位线绘制的内核
摆动高/低的确认不是看当前柱本身,而是拿它和左右邻柱比高低。若当前柱 high 不大于等于右侧、或小于左侧 high,isSwingHigh 直接置 false;low 同理,任一侧不满足就取消摆动低标记。 当 isSwingHigh 仍为真,才把 high(curr_bar) 存入 swing_H,用 Print 打出 bar 索引与价格,并调用 drawSwingPoint 画蓝色箭头(style 77、偏移 -1)。摆动低则存 swing_L、打红色箭头、偏移 +1,视觉上一目了然。 破位线由 drawBreakLevel 单独封装:先用 ObjectFind 判重,不存在才 ObjectCreate 生成 OBJ_ARROWED_LINE。四个坐标点分两次 SetInteger/SetDouble 写入 time1/price1 与 time2/price2,最后 SetInteger 定颜色。 在 MT5 里把 direction 参数接到 BOS 突破方向,黄金 15 分钟图测试时箭头偏移 ±1 像素常不够显眼,可改 ±15 验证辨识度。外汇与贵金属波动剧烈,摆动点仅代表历史结构,后续反转概率随流动性变化。
if ( (high(curr_bar,timeframe_bos) <= high(right_index,timeframe_bos)) || (high(curr_bar,timeframe_bos) < high(left_index,timeframe_bos)) ){ isSwingHigh = class="kw">false; } if ( (low(curr_bar,timeframe_bos) >= low(right_index,timeframe_bos)) || (low(curr_bar,timeframe_bos) > low(left_index,timeframe_bos)) ){ isSwingLow = class="kw">false; } } if (isSwingHigh){ swing_H = high(curr_bar,timeframe_bos); Print("WE DO HAVE A SWING HIGH @ BAR INDEX ",curr_bar," H: ",high(curr_bar,timeframe_bos)); drawSwingPoint(TimeToString(time(curr_bar,timeframe_bos)),time(curr_bar,timeframe_bos),high(curr_bar,timeframe_bos),class="num">77,clrBlue,-class="num">1); } if (isSwingLow){ swing_L = low(curr_bar,timeframe_bos); Print("WE DO HAVE A SWING LOW @ BAR INDEX ",curr_bar," L: ",low(curr_bar,timeframe_bos)); drawSwingPoint(TimeToString(time(curr_bar,timeframe_bos)),time(curr_bar,timeframe_bos),low(curr_bar,timeframe_bos),class="num">77,clrRed,+class="num">1); } } class="type">void drawBreakLevel(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">color clr,class="type">int direction){ if (ObjectFind(class="num">0,objName) < class="num">0){ ObjectCreate(class="num">0,objName,OBJ_ARROWED_LINE,class="num">0,time1,price1,time2,price2); ObjectSetInteger(class="num">0,objName,OBJPROP_TIME,class="num">0,time1); ObjectSetDouble(class="num">0,objName,OBJPROP_PRICE,class="num">0,price1); ObjectSetInteger(class="num">0,objName,OBJPROP_TIME,class="num">1,time2); ObjectSetDouble(class="num">0,objName,OBJPROP_PRICE,class="num">1,price2); ObjectSetInteger(class="num">0,objName,OBJPROP_COLOR,clr);
◍ 突破摆荡高点时的图形标注与信号触发
在 MT5 自定义指标里,把摆荡高点(swing high)被刺穿的瞬间画出来,比单纯打印日志更有用。下面这段逻辑先给突破线设 2 像素宽度,再在线的末端挂一个「Break」文字标签,向上突破锚定右上、向下突破锚定右下,颜色沿用通道色,字号固定 10。 真正触发信号的是实时价与 swing_H 的比较:用 NormalizeDouble 把 SymbolInfoDouble 取到的 SYMBOL_ASK 收敛到当前品种小数位,一旦 Ask 大于 swing_H 就 Print 出「BUY SIGNAL NOW」并反向回扫最多 length*2+1000 根 K 线,定位 swing_H 所在的 bar index。实盘里 EURUSD 的 _Digits 通常是 5,XAUUSD 常见 2,归一化漏掉就会画出偏移的线。 回扫循环不是无限跑,上限 length*2+1000 根是基于摆荡识别窗口做的经验截断;若你的 length 设 50,那最多翻 1100 根,够覆盖绝大多数次级折返。外汇与贵金属杠杆高、滑点跳空频繁,Ask 瞬时刺穿 swing_H 可能只是假突破,信号仅代表概率倾向,需结合更高周期确认。 调用 drawBreakLevel 时传入当前时间、swing 高所在 bar 的时间与高价,图表重绘后突破位就钉在图上了。开 MT5 把这段接进你已有的 BOS 函数,调一下 OBJPROP_FONTSIZE 看标注是否挡视线,比看文字描述直观。
ObjectSetInteger(class="num">0,objName,OBJPROP_WIDTH,class="num">2); class=class="str">"cmt">//--- Set the width of the line class="type">class="kw">string text = "Break"; class=class="str">"cmt">//--- Define the text label for the break class="type">class="kw">string objName_Descr = objName + text; class=class="str">"cmt">//--- Create a unique name for the text object ObjectCreate(class="num">0,objName_Descr,OBJ_TEXT,class="num">0,time2,price2); class=class="str">"cmt">//--- Create a text object at the line&class="macro">#x27;s end ObjectSetInteger(class="num">0,objName_Descr,OBJPROP_COLOR,clr); class=class="str">"cmt">//--- Set the class="type">color of the text ObjectSetInteger(class="num">0,objName_Descr,OBJPROP_FONTSIZE,class="num">10); class=class="str">"cmt">//--- Set the font size of the text if (direction > class="num">0) { class=class="str">"cmt">//--- Check if the break is upward ObjectSetString(class="num">0,objName_Descr,OBJPROP_TEXT,text+" "); class=class="str">"cmt">//--- Set the text content ObjectSetInteger(class="num">0,objName_Descr,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER); class=class="str">"cmt">//--- Set the text anchor to right upper } if (direction < class="num">0) { class=class="str">"cmt">//--- Check if the break is downward ObjectSetString(class="num">0,objName_Descr,OBJPROP_TEXT,text+" "); class=class="str">"cmt">//--- Set the text content ObjectSetInteger(class="num">0,objName_Descr,OBJPROP_ANCHOR,ANCHOR_RIGHT_LOWER); class=class="str">"cmt">//--- Set the text anchor to right lower } } ChartRedraw(class="num">0); class=class="str">"cmt">//--- Redraw the chart to display the break level } class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class=class="str">"cmt">//--- Get and normalize the current Ask price class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">//--- Get and normalize the current Bid price if (swing_H > class="num">0 && Ask > swing_H){ class=class="str">"cmt">//--- Check if the Ask price breaks above the swing high Print("$$$$$$$$$ BUY SIGNAL NOW. BREAK OF SWING HIGH"); class=class="str">"cmt">//--- Log a buy signal due to swing high breakout class="type">int swing_H_index = class="num">0; class=class="str">"cmt">//--- Initialize the index of the swing high bar for (class="type">int i=class="num">0; i<=length*class="num">2+class="num">1000; i++){ class=class="str">"cmt">//--- Loop through bars to find the swing high class="type">class="kw">double high_sel = high(i,timeframe_bos); class=class="str">"cmt">//--- Get the high price of the i-th bar if (high_sel == swing_H){ class=class="str">"cmt">//--- Check if the high matches the swing high swing_H_index = i; class=class="str">"cmt">//--- Store the bar index Print("BREAK HIGH FOUND @ BAR INDEX ",swing_H_index); class=class="str">"cmt">//--- Log the swing high bar index break; class=class="str">"cmt">//--- Exit the loop once found } } drawBreakLevel(TimeToString(time(class="num">0,timeframe_bos)),time(swing_H_index,timeframe_bos),high(swing_H_index,timeframe_bos),
「摆动高低点突破后的下单与画线逻辑」
当 Bid 跌破记录的摆动低点 swing_L 时,脚本先向后回溯 length*2+1000 根 K 线定位该摆动低点所在 bar 的索引,再用 drawBreakLevel 在图上拉一条红色水平线标出破位位置。若当前尚未持仓(isTakenTrade == false),则直接以 0.01 手卖出,Bid 进场,maximum_price 作止损、minimum_price 作止盈,随后把 swing_L 重置为 -1.0 并 return 跳出 OnTick,避免同根 K 线重复触发。 买侧对称:Ask 上破 swing_H 且破位点落在 minimum_price 与 maximum_price 区间内时,打印买信号并循环找 swing_H_index,蓝色线标记突破位;无持仓则 0.01 手买入,Ask 进场,minimum_price 止损、maximum_price 止盈。外汇与贵金属杠杆高,实盘前务必在 MT5 策略测试器用 0.01 手验证滑点与回撤,这类区间突破信号在历史中约有概率在震荡市连续假突破。 别把 0.01 手当安全垫 代码里写死 0.01 手只是样例仓位,黄金 XAUUSD 一个 5 美元波动就吞掉 0.05 手等效亏损,真上 MT5 请按账户净值重算手数,别直接拷去跑实盘。
if (swing_L > class="num">0 && Bid < swing_L){ Print("$$$$$$$$$ SELL SIGNAL NOW. BREAK OF SWING LOW"); class="type">int swing_L_index = class="num">0; for (class="type">int i=class="num">0; i<=length*class="num">2+class="num">1000; i++){ class="type">class="kw">double low_sel = low(i,timeframe_bos); if (low_sel == swing_L){ swing_L_index = i; Print("BREAK LOW FOUND @ BAR INDEX ",swing_L_index); break; } } drawBreakLevel(TimeToString(time(class="num">0,timeframe_bos)),time(swing_L_index,timeframe_bos),low(swing_L_index,timeframe_bos), time(class="num">0,timeframe_bos),low(swing_L_index,timeframe_bos),clrRed,+class="num">1); if (isTakenTrade == class="kw">false){ obj_Trade.Sell(class="num">0.01,_Symbol,Bid,maximum_price,minimum_price); isTakenTrade = true; } swing_L = -class="num">1.0; class="kw">return; } if (swing_H > class="num">0 && Ask > swing_H && swing_H <= maximum_price && swing_H >= minimum_price){ Print("$$$$$$$$$ BUY SIGNAL NOW. BREAK OF SWING HIGH WITHIN RANGE"); class="type">int swing_H_index = class="num">0; for (class="type">int i=class="num">0; i<=length*class="num">2+class="num">1000; i++){ class="type">class="kw">double high_sel = high(i,timeframe_bos); if (high_sel == swing_H){ swing_H_index = i;
摆点破位后的下单与画线落点
当 Bid 向下击穿 swing_L 且落在 [minimum_price, maximum_price] 区间内,系统判定为摆点破位并输出 SELL SIGNAL。循环最多扫 length*2+1000 根 K 线,定位与 swing_L 相等的低点所在 bar 索引,用于后续画线锚定。 drawBreakLevel 用当前周期时间、破位 bar 的 low 值与当前时间拼出一条红色水平线,方向参数 +1 表示向下破位标记。若 isTakenTrade 仍为 false,则以 0.01 手卖出,止损挂 maximum_price、止盈挂 minimum_price——注意这里多空止损止盈是反置的,回测里需确认该逻辑是否符合你的风控预期。 破位处理完立刻把 swing_L 重置为 -1.0 并 return,避免 OnTick 里重复触发。外汇与贵金属杠杆高,这种破位追单在滑点行情中可能连续止损,建议先在 MT5 策略测试器用 0.01 手跑一轮 EURUSD 15M 验证信号密度。
Print("BREAK HIGH FOUND @ BAR INDEX ",swing_H_index); class=class="str">"cmt">//--- Log the swing high bar index break; class=class="str">"cmt">//--- Exit the loop once found } } drawBreakLevel(TimeToString(time(class="num">0,timeframe_bos)),time(swing_H_index,timeframe_bos),high(swing_H_index,timeframe_bos), time(class="num">0,timeframe_bos),high(swing_H_index,timeframe_bos),clrBlue,-class="num">1); class=class="str">"cmt">//--- Draw a line to mark the swing high breakout if (isTakenTrade == class="kw">false){ class=class="str">"cmt">//--- Check if no trade is taken yet obj_Trade.Buy(class="num">0.01,_Symbol,Ask,minimum_price,maximum_price); class=class="str">"cmt">//--- Execute a buy trade with class="num">0.01 lots, class="kw">using minimum price as SL and maximum as TP isTakenTrade = true; class=class="str">"cmt">//--- Set the flag to indicate a trade is taken } swing_H = -class="num">1.0; class=class="str">"cmt">//--- Reset the swing high price class="kw">return; class=class="str">"cmt">//--- Exit the OnTick function to avoid further processing } if (swing_L > class="num">0 && Bid < swing_L && swing_L <= maximum_price && swing_L >= minimum_price){ class=class="str">"cmt">//--- Check if the Bid price breaks below the swing low within the range Print("$$$$$$$$$ SELL SIGNAL NOW. BREAK OF SWING LOW WITHIN RANGE"); class=class="str">"cmt">//--- Log a sell signal due to swing low breakout class="type">int swing_L_index = class="num">0; class=class="str">"cmt">//--- Initialize the index of the swing low bar for (class="type">int i=class="num">0; i<=length*class="num">2+class="num">1000; i++){ class=class="str">"cmt">//--- Loop through bars to find the swing low class="type">class="kw">double low_sel = low(i,timeframe_bos); class=class="str">"cmt">//--- Get the low price of the i-th bar if (low_sel == swing_L){ class=class="str">"cmt">//--- Check if the low matches the swing low swing_L_index = i; class=class="str">"cmt">//--- Store the bar index Print("BREAK LOW FOUND @ BAR INDEX ",swing_L_index); class=class="str">"cmt">//--- Log the swing low bar index break; class=class="str">"cmt">//--- Exit the loop once found } } drawBreakLevel(TimeToString(time(class="num">0,timeframe_bos)),time(swing_L_index,timeframe_bos),low(swing_L_index,timeframe_bos), time(class="num">0,timeframe_bos),low(swing_L_index,timeframe_bos),clrRed,+class="num">1); class=class="str">"cmt">//--- Draw a line to mark the swing low breakout if (isTakenTrade == class="kw">false){ class=class="str">"cmt">//--- Check if no trade is taken yet obj_Trade.Sell(class="num">0.01,_Symbol,Bid,maximum_price,minimum_price); class=class="str">"cmt">//--- Execute a sell trade with class="num">0.01 lots, class="kw">using maximum price as SL and maximum as TP isTakenTrade = true; class=class="str">"cmt">// //--- Set the flag to indicate a trade is taken } swing_L = -class="num">1.0; class=class="str">"cmt">//--- Reset the swing low price class="kw">return; class=class="str">"cmt">//--- Exit the OnTick function to avoid further processing }
◍ 把这套系统当起点而非终点
午夜区间叠加 BOS 确认的 EA 已经跑通:它先锁定当日关键拐点界定出的午夜区间,再在区间内等待结构突破信号自动下单,并在图表上把区间和信号画出来方便肉眼复核。 社区里有人拿 AUDUSD M15 之外测了 AUDJPY、USDJPY,回测夏普落在 -3.00 到 -5.00,账户曲线直接翻绿且没回头;这说明该逻辑对货币对和时段极其挑嘴,外汇与贵金属本身高杠杆、高波动,实盘前必须自己用 MT5 策略测试器换品种复跑。 真要落地,建议先下载附带的 Midnight_Range_Break_of_Structure_Breakout.mq5,把 TimeFrame 和 RangeHours 两个输入调开,只改参数、不碰核心判断,看哪组样本外数据能守住盈亏平衡。 它能扩展的方向很实——加一层波动率过滤、或把 BOS 的摆动阈值跟 ATR 挂钩,都可能让信号更耐打,剩下的交给你的交易风格去磨。