MQL5 简介(第 17 部分):构建趋势反转 EA 交易·综合运用
📘

MQL5 简介(第 17 部分):构建趋势反转 EA 交易·综合运用

第 3/3 篇

用最近四根柱捕捉趋势线的突破与反转

在 MT5 里做趋势线交易,核心不是把整段历史都扫一遍,而是盯住最后四根已收盘的柱。突破和反转大多发生在最近行情里,扫全图反而慢半拍。MQL5 的内置函数能直接按时间取任意趋势线在指定柱的价格水平,这让“价与线互动”的判断可以纯程序化。 需要注意一个细节:我们用 CopyTime() 从索引 1 开始复制,跳过最右边那根还没收盘的 ticking bar。代码里的“第 0 柱”其实是图表上从右数第二根——它已经收盘,数据可靠。把趋势线在第 0、1、2、3 根柱的值分别存进 t_line_value、t1_line_value、t2_line_value、t3_line_value,再用 Comment() 打印到左上角,肉眼就能比对线位变化。 反转不是“碰到线就买”那么简单。影线触线后收阳、阴线触线后下一根阳线确认、连跌几根才出阳线、以及假突破后立刻看涨吞回,都算合格反转;但若是两根阴线已经实穿趋势线,后面再出阳线也只是突破后的回测噪音,不能当作反转信号。算法里要用 prev_touch 类标志锁住第一次有效信号,否则同一波反转会触发多次进场。 突破回测的逻辑正好反过来:价格跌破上升趋势线后,若影线回抽触碰原支撑(已转阻力)但开盘价在线下、随后阴线确认,就下卖单。下降趋势线同理,只是把方向颠倒——重点找看跌反转和看涨突破。外汇与贵金属杠杆高,趋势线被假突破扫损是常态,任何信号都只是概率倾向,实盘前务必用策略测试器跑近 3 个月数据验证。

◍ 用两次摆动低点把上升趋势线画出来

上升趋势线的自动绘制,核心是先抓最近的一个摆动低点,再往前找一个更低的摆动低点。代码里用两次循环完成:第一次从 LookbackBars 扫到 bars_check - LookbackBars,遇到 IsSwingLow 就记 first_low 和 first_low_time 然后 break;第二次同样区间,要求 low_price[i] < first_low 且时间更早,找到即记 second_low。 两个低点确认后,用 ObjectCreate 以 second_low_time/second_low 到 first_low_time/first_low 拉出 OBJ_TREND。先设 OBJPROP_TIMEFRAMES 为 OBJ_NO_PERIODS 暂时全周期隐藏,避免半成品线闪现。 只有当 first_low > second_low 且 second_low > 0 时结构才有效,此时开 OBJPROP_RAY_RIGHT 向右延伸,TIMEFRAMES 改 OBJ_ALL_PERIODS 显示,颜色 clrBlue、宽度 3。外汇与贵金属波动剧烈,此类自动画线在高波动时段可能频繁重绘,需以实盘前回测验证。 画线后立刻取线在最近四根 bar 对应时间的价位:t_line_value 取 time_price[0](当前 bar),t1_line_value 取 time_price[1](一根前),t2_line_value 取 time_price[2](两根前)。这几个值后续可用于判断价格与趋势线的距离关系,建议在 MT5 里打印出来核对与肉眼画线偏差。

MQL5 / C++
class="type">class="kw">double t_line_value;   class=class="str">"cmt">// Ascending trend line price level at the time of the most recent bar(not the ticking bar)
class="type">class="kw">double t1_line_value;  class=class="str">"cmt">// Ascending trend line price level at the time of the second most recent bar
class="type">class="kw">double t2_line_value;  class=class="str">"cmt">// Ascending trend line price level at the time of the third most recent bar
class="type">class="kw">double t3_line_value;  class=class="str">"cmt">// Ascending trend line price level at the time of the fourth most recent bar
if(allow_uptrend)
  {
class=class="str">"cmt">// First loop: Find the most recent swing low(first low)
   for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
     {
      class=class="str">"cmt">// Check if current point is a swing low
      if(IsSwingLow(low_price, i, LookbackBars))
        {
         class=class="str">"cmt">// Store price and time of the first(latest) swing low
         first_low = low_price[i];
         first_low_time = time_price[i];
         break;   class=class="str">"cmt">// Exit loop after finding the first swing low
        }
     }
class=class="str">"cmt">// Second loop: Find an earlier swing low that is lower than the first low
   for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
     {
      class=class="str">"cmt">// Check for earlier swing low that is lower and occurs before the first low
      if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time)
        {
         class=class="str">"cmt">// Store price and time of the second(older) swing low
         second_low = low_price[i];
         second_low_time = time_price[i];
         break;   class=class="str">"cmt">// Exit loop after finding the second swing low
        }
     }
class=class="str">"cmt">// Create an ascending trend line from the second low to the first low
   ObjectCreate(chart_id, up_trend, OBJ_TREND, class="num">0, second_low_time, second_low, first_low_time, first_low);
   ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);   class=class="str">"cmt">// Temporarily hide line on all timeframes
class=class="str">"cmt">// If the swing structure is valid(i.e., second low is lower than first)
   if(first_low > second_low && second_low > class="num">0)
     {
      class=class="str">"cmt">// Extend the trend line to the right
      ObjectSetInteger(chart_id, up_trend, OBJPROP_RAY_RIGHT, true);
      class=class="str">"cmt">// Show the trend line on all timeframes
      ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
      class=class="str">"cmt">// Set visual properties: class="type">color and thickness
      ObjectSetInteger(chart_id, up_trend, OBJPROP_COLOR, clrBlue);
      ObjectSetInteger(chart_id, up_trend, OBJPROP_WIDTH, class="num">3);
      class=class="str">"cmt">// Get the price values of the trend line at the corresponding times of the four most recent bars
      t_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">0], class="num">0);   class=class="str">"cmt">// Current bar
      t1_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">1], class="num">0);  class=class="str">"cmt">// One bar ago
      t2_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">2], class="num">0);  class=class="str">"cmt">// Two bars ago

「参数面板与数据结构怎么摆」

这段脚本把交易开关和风控参数全摊在 input 区,方便在 MT5 属性框里直接调。趋势线绘制、突破/反转下单、手数、止损止盈点数各自独立成变量,改一个不影响其余逻辑。 lookbackBars 设为 5,代表找摆动低点时只回看最近 5 根 K 线;lot_size 给的是 0.6 标准手,sl_points=10、tp_points=50,盈亏比倾向 5:1,但外汇和贵金属杠杆高,实际回撤可能远超预设点数。 下方声明了一组价格数组:close_price、open_price、low_price、high_price 以及 time_price,用来缓存 500 根历史 bars(bars_check=500)的 OHLC 与时间。first_low / second_low 配合各自时间字段,专门接住两个摆动低点,供后面画上升趋势线用。 代码里还顺手取了 chart_id = ChartID(),后续 ObjectGetValueByTime 读趋势线在指定时间的数值就靠它定位图表。

MQL5 / C++
t3_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">3], class="num">0);   class=class="str">"cmt">// Three bars ago
   Comment("Ascending trend tine value for the last class="num">4 Bars",
           "\nBar class="num">0: ",  DoubleToString(t_line_value, _Digits),
           "\nBar class="num">1: ", DoubleToString(t1_line_value, _Digits),
           "\nBar class="num">2: ",  DoubleToString(t2_line_value, _Digits),
           "\nBar class="num">3: ",  DoubleToString(t3_line_value, _Digits));
   }
}
class="macro">#include <Trade/Trade.mqh>
CTrade trade;
class="type">int MagicNumber = class="num">532127;
class=class="str">"cmt">// Timeframe to use for retrieving candlestick data(class="kw">default is the current chart timeframe)
input ENUM_TIMEFRAMES time_frame = PERIOD_CURRENT;
class=class="str">"cmt">// Input to enable or disable drawing of the ascending trend line(true = allow drawing)
input class="type">bool allow_uptrend = true;
class=class="str">"cmt">// Number of candles to look back when identifying swing lows for drawing the trend line
input class="type">int LookbackBars = class="num">5;
class=class="str">"cmt">// Input to enable or disable drawing of the descebding trend line(true = allow drawing)
input class="type">bool allow_downtrend = true;
input class="type">bool allow_break_out = true;    class=class="str">"cmt">// Enable or disable trade execution on trend line breakout(true = allow)
input class="type">bool allow_reversal = true;     class=class="str">"cmt">// Enable or disable trade execution on trend line reversal(true = allow)
input class="type">class="kw">double lot_size = class="num">0.6;          class=class="str">"cmt">// Lot size for each trade
input class="type">class="kw">double sl_points = class="num">10;          class=class="str">"cmt">// Stop Loss in points from entry price
input class="type">class="kw">double tp_points = class="num">50;          class=class="str">"cmt">// Take Profit in points from entry price
class=class="str">"cmt">// Number of past bars(candlesticks) to check
class="type">int bars_check = class="num">500;
class=class="str">"cmt">// Arrays to store candlestick data
class="type">class="kw">double close_price[];   class=class="str">"cmt">// Stores close prices
class="type">class="kw">double open_price[];    class=class="str">"cmt">// Stores open prices
class="type">class="kw">double low_price[];     class=class="str">"cmt">// Stores low prices
class="type">class="kw">double high_price[];    class=class="str">"cmt">// Stores high prices
class="type">class="kw">datetime time_price[];  class=class="str">"cmt">// Stores time data for each candle
class="type">class="kw">double first_low;       class=class="str">"cmt">// Price value of the first identified swing low
class="type">class="kw">datetime first_low_time; class=class="str">"cmt">// Time when the first swing low occurred
class="type">class="kw">double second_low;      class=class="str">"cmt">// Price value of the second identified swing low
class="type">class="kw">datetime second_low_time; class=class="str">"cmt">// Time when the second swing low occurred
class="type">class="kw">string up_trend = "Up Trend";  class=class="str">"cmt">// Label used to name the ascending trend line object on the chart
class="type">long chart_id = ChartID();     class=class="str">"cmt">// Stores the current chart ID for referencing during object creation or manipulation
class="type">class="kw">double first_high;      class=class="str">"cmt">// Price value of the first identified swing high(latest high)

摆动高点与趋势线变量的落地写法

这段声明把下降结构里的两个摆动高点分别存了价格和时间:first_high_time 记最近一次摆动高点发生时刻,second_high 与 second_high_time 则锁住更早那根高点。down_trend 只是给图表下降趋势线对象打的标签,方便后续 ObjectCreate 直接复用。 t_line_value 到 t3_line_value 四个变量,分别取最近一根、倒数第二、第三、第四根已收盘 K 线处的上升趋势线价位。注意这里刻意避开「正在 tick 的当下根」,只用已闭合的 bar 算值,能躲掉实时毛刺造成的假突破信号。 lookbackf_time 用来框定回看边界,lastTradeBarTime 初始为 0,后面靠它防止同一根 bar 重复触发。OnInit 里把 close/open/low/high/time 五个数组全设成系列排列,索引 0 即最新 bar;trade.SetExpertMagicNumber 把魔数绑给交易对象。 OnDeinit 只干一件事:ObjectsDeleteAll 清掉图表上本 EA 画的线,避免切换周期留下垃圾。OnTick 开头用 CopyOpen/CopyClose/CopyLow/CopyHigh/CopyTime 从偏移 1 开始抓 bars_check 根历史数据——跳过第 0 根正是为了只用定型 K 线。最后一句取 SYMBOL_ASK 实时卖价,供后面判突破用。外汇与贵金属波动剧烈,这类结构仅作概率参考,实盘前请在 MT5 策略测试器跑至少 3 个月 tick 数据。

MQL5 / C++
class="type">class="kw">datetime first_high_time;   class=class="str">"cmt">// Time when the first swing high occurred
class="type">class="kw">double second_high;       class=class="str">"cmt">// Price value of the second identified swing high(older high)
class="type">class="kw">datetime second_high_time; class=class="str">"cmt">// Time when the second swing high occurred
class="type">class="kw">string down_trend = "Down Trend"; class=class="str">"cmt">// Label used to name the descending trend line object on the chart
class="type">class="kw">double t_line_value;     class=class="str">"cmt">// Ascending trend line price level at the time of the most recent bar(not the ticking bar)
class="type">class="kw">double t1_line_value;    class=class="str">"cmt">// Ascending trend line price level at the time of the second most recent bar
class="type">class="kw">double t2_line_value;    class=class="str">"cmt">// Ascending trend line price level at the time of the third most recent bar
class="type">class="kw">double t3_line_value;    class=class="str">"cmt">// Ascending trend line price level at the time of the fourth most recent bar
class=class="str">"cmt">// Time boundary used to limit lookback for valid reversal setups
class="type">class="kw">datetime lookbackf_time;
class="type">class="kw">datetime lastTradeBarTime = class="num">0;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">// Set arrays as series so the newest bar is index class="num">0 ( start from the latest bar)
   ArraySetAsSeries(close_price, true);
   ArraySetAsSeries(open_price, true);
   ArraySetAsSeries(low_price, true);
   ArraySetAsSeries(high_price, true);
   ArraySetAsSeries(time_price, true);
   trade.SetExpertMagicNumber(MagicNumber);
   class="kw">return(INIT_SUCCEEDED);  class=class="str">"cmt">// Signal that the EA initialized successfully
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   ObjectsDeleteAll(chart_id);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">// Copy the latest candlestick data into the arrays
   CopyOpen(_Symbol, time_frame, class="num">1, bars_check, open_price);     class=class="str">"cmt">// Open prices
   CopyClose(_Symbol, time_frame, class="num">1, bars_check, close_price);   class=class="str">"cmt">// Close prices
   CopyLow(_Symbol, time_frame, class="num">1, bars_check, low_price);       class=class="str">"cmt">// Low prices
   CopyHigh(_Symbol, time_frame, class="num">1, bars_check, high_price);     class=class="str">"cmt">// High prices
   CopyTime(_Symbol, time_frame, class="num">1, bars_check, time_price);     class=class="str">"cmt">// Candle times
   class="type">class="kw">double ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);

◍ 上升趋势线的双低点抓取逻辑

在 MT5 里画一条靠谱的上升趋势线,核心不是连两个点,而是先确认摆动低点(swing low)的结构顺序。下面这段逻辑只处理用户允许绘制上升线的情况,用两次循环分别从近到远找低点。 第一次循环从 LookbackBars 扫到 bars_check - LookbackBars,调用 IsSwingLow 判定当前 Bar 是否为摆动低点,命中就记录 first_low 与 first_low_time,并顺手存了 i-3 的 time 作参考,随后直接 break。第二次循环同样区间,但要求低点时间早于 first_low_time 且价格更低,找到后记为 second_low / second_low_time,这就是趋势线的「远端锚点」。 锚点齐了就用 ObjectCreate 以 OBJ_TREND 画出来,先设 OBJPROP_TIMEFRAMES 为 OBJ_NO_PERIODS 临时隐藏。只有当 first_low > second_low 且 second_low > 0 时,才把 OBJPROP_RAY_RIGHT 开成 true 向右延伸,并改 OBJ_ALL_PERIODS 全周期显示,颜色蓝、线宽 3。 最后用 ObjectGetValueByTime 取当前 Bar 时间对应趋势线价位 t_line_value,后续拿它和实时价比,就能判断价格是否还在线上方。外汇与贵金属波动剧烈,这类画线仅描述已有结构,不代表后续一定沿趋势运行,实盘须自担高风险。

MQL5 / C++
class="type">class="kw">datetime currentBarTime = iTime(_Symbol, time_frame, class="num">0);
class=class="str">"cmt">// If the user allows drawing of ascending trend line
if(allow_uptrend)
  {
  class=class="str">"cmt">// First loop: Find the most recent swing low(first low)
  for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
    {
    class=class="str">"cmt">// Check if current point is a swing low
    if(IsSwingLow(low_price, i, LookbackBars))
      {
      class=class="str">"cmt">// Store price and time of the first(latest) swing low
      first_low = low_price[i];
      first_low_time = time_price[i];
      lookbackf_time = time_price[i - class="num">3];
      break;  class=class="str">"cmt">// Exit loop after finding the first swing low
      }
    }
  class=class="str">"cmt">// Second loop: Find an earlier swing low that is lower than the first low
  for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
    {
    class=class="str">"cmt">// Check for earlier swing low that is lower and occurs before the first low
    if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time)
      {
      class=class="str">"cmt">// Store price and time of the second(older) swing low
      second_low = low_price[i];
      second_low_time = time_price[i];
      break;  class=class="str">"cmt">// Exit loop after finding the second swing low
      }
    }
  class=class="str">"cmt">// Create an ascending trend line from the second low to the first low
  ObjectCreate(chart_id, up_trend, OBJ_TREND, class="num">0, second_low_time, second_low, first_low_time, first_low);
  ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);  class=class="str">"cmt">// Temporarily hide line on all timeframes
  class=class="str">"cmt">// If the swing structure is valid(i.e., second low is lower than first)
  if(first_low > second_low && second_low > class="num">0)
    {
    class=class="str">"cmt">// Extend the trend line to the right
    ObjectSetInteger(chart_id, up_trend, OBJPROP_RAY_RIGHT, true);
    class=class="str">"cmt">// Show the trend line on all timeframes
    ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
    class=class="str">"cmt">// Set visual properties: class="type">color and thickness
    ObjectSetInteger(chart_id, up_trend, OBJPROP_COLOR, clrBlue);
    ObjectSetInteger(chart_id, up_trend, OBJPROP_WIDTH, class="num">3);
    class=class="str">"cmt">// Get the price values of the trend line at the corresponding times of the four most recent bars
    t_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">0], class="num">0);  class=class="str">"cmt">// Current bar
    }

「回踩趋势线的反转 Wick 判定逻辑」

在上升趋势线策略里,光画好线不够,得确认价格是不是真的在线上方完成了「刺透再收回」的动作。下面这段逻辑专门在最近 4 根 bar 内扫描下影线跌破趋势线、但开盘价仍在线上方的形态,这往往暗示回调抛压被接住,后续存在多头重新控场的可能。 扫描从当前 bar 往前数 i=0 到 3,只要某根 bar 的 low 低于对应时刻趋势线值、且 open 高于该值,就记为一次有效反转区接触;随后从接触点往当前回看,找第一根实体收在趋势线上方的阳线作为确认信号,并用 Bars() 算出它距离现在多少根。外汇与贵金属杠杆高,这类刺透形态失败率不低,需结合更大周期过滤。 为了避免同一处反转区反复发信号,代码末尾还检查了前 1~2 根 bar 是否 already 出现过「下影触线 + 阳线实体」的组合(prev_touch 标记)。若已出现过,则本次不再重复触发,实盘里能少挨几次假突破的巴掌。 开 MT5 把这段塞进 EA 的 OnCalculate 里,把 up_trend 换成你手动或自动画的上升线名,跑 EURUSD 的 M15 就能看到 no_bars 在反转确认后通常落在 0~3 之间。

MQL5 / C++
t1_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">1], class="num">0);   class=class="str">"cmt">// One bar ago
  t2_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">2], class="num">0);   class=class="str">"cmt">// Two bars ago
  t3_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">3], class="num">0);   class=class="str">"cmt">// Three bars ago
  class=class="str">"cmt">// Number of bars between the valid bullish confirmation candle and current time
  class="type">int no_bars = class="num">0;
  class=class="str">"cmt">// Loop through the last class="num">4 bars to check for reversal wick touch on the trend line
  for(class="type">int i = class="num">0; i <= class="num">3; i++)
    {
    class=class="str">"cmt">// Condition: Wick of the candle touches below the trend line but opens above it(indicating a potential reversal zone)
    if(low_price[i] < ObjectGetValueByTime(chart_id, up_trend, time_price[i], class="num">0) &&
        open_price[i] > ObjectGetValueByTime(chart_id, up_trend, time_price[i], class="num">0))
      {
      class=class="str">"cmt">// Check if there&class="macro">#x27;s a bullish confirmation candle after the wick touch(within or immediately after)
      for(class="type">int j = i; j >= class="num">0; j--)
        {
        class=class="str">"cmt">// Bullish candle that closed above the trend line
        if(close_price[j] > open_price[j] &&
            close_price[j] > ObjectGetValueByTime(chart_id, up_trend, time_price[j], class="num">0))
          {
          class=class="str">"cmt">// Count how many bars ago this confirmation occurred
          no_bars = Bars(_Symbol, time_frame, time_price[j], TimeCurrent());
          break;
          }
        }
      break; class=class="str">"cmt">// Exit after first valid reversal zone is found
      }
    }
  class=class="str">"cmt">// Check whether a similar wick touch(reversal) happened recently to avoid repeated signals
  class="type">bool prev_touch = false;
  if((low_price[class="num">1] < t1_line_value && close_price[class="num">1] > open_price[class="num">1]) ||  class=class="str">"cmt">// Bar class="num">1 had reversal wick and bullish body
      (low_price[class="num">2] < t2_line_value && close_price[class="num">2] > open_price[class="num">2]))    class=class="str">"cmt">// Bar class="num">2 had reversal wick and bullish body
    {

下降趋势线反转买点的条件拼装

这段逻辑只处理下降趋势线启用后的买点判定,核心是先在前文标记出最近的一个摆动高点,再拿它去连更早的高点形成趋势线。代码里用两段循环:第一段从 LookbackBars 扫到 bars_check - LookbackBars,遇到 IsSwingHigh 就存下 first_high 和 first_high_time 并 break,保证只取最新那个摆动高点。 买触发放在另一个 if 块里,要求最近 4 根 K 线中至少有一根下影刺穿趋势线但开盘在线上方(low < t_line_value && open > t_line_value,依次检查 0~3 号 bar 对应的 t/t1/t2/t3 线值),且当前阳线收在趋势线上方。 确认棒必须在触碰后 3 根内(no_bars < 3),同时 prev_touch 为 false 避免重复信号,time_price[3] 要晚于 lookbackf_time 阈值,且 allow_reversal 开启、currentBarTime 不等于 lastTradeBarTime。全部满足才 trade.Buy,并用 lastTradeBarTime 更新防重。 外汇与贵金属杠杆高,这类刺穿反转信号在历史回测中假突破概率不低,实盘前建议在 MT5 策略测试器用 2020—2024 年 XAUUSD 小时图跑一遍,把 sl_points / tp_points 拆开看盈亏比分布。

MQL5 / C++
prev_touch = true; class=class="str">"cmt">// Flag that a recent touch already occurred
     }
     class=class="str">"cmt">// Final condition for executing a BUY trade on a reversal setup
     if(
       class=class="str">"cmt">// One of the recent class="num">4 bars touched and rejected the trend line(wick below, open above), AND
       ((low_price[class="num">0] < t_line_value && open_price[class="num">0] > t_line_value) ||
        (low_price[class="num">1] < t1_line_value && open_price[class="num">1] > t1_line_value) ||
        (low_price[class="num">2] < t2_line_value && open_price[class="num">2] > t2_line_value) ||
        (low_price[class="num">3] < t3_line_value && open_price[class="num">3] > t3_line_value))
       &&
       class=class="str">"cmt">// Current candle must be bullish and close above the trend line
       (close_price[class="num">0] > open_price[class="num">0]) && close_price[class="num">0] > t_line_value
       &&
       class=class="str">"cmt">// The bullish confirmation must occur within class="num">3 bars
       (no_bars < class="num">3)
       &&
       class=class="str">"cmt">// No recent wick reversal signal already processed
       prev_touch == false
       &&
       class=class="str">"cmt">// The signal must be more recent than the lookback time threshold
       (time_price[class="num">3] > lookbackf_time)
       &&
       class=class="str">"cmt">// Reversal signals are allowed and this signal is not duplicated from the same bar
       (allow_reversal == true && currentBarTime != lastTradeBarTime)
       )
       {
         class=class="str">"cmt">// Execute BUY trade with defined lot size, SL and TP
         trade.Buy(lot_size, _Symbol, ask_price, ask_price - sl_points, ask_price + tp_points);
         lastTradeBarTime = currentBarTime; class=class="str">"cmt">// Update last trade bar time to avoid duplicate signals
       }
     }
  }
class=class="str">"cmt">//
class=class="str">"cmt">// Only proceed if drawing descending trend lines is enabled
   if(allow_downtrend)
   {
     class=class="str">"cmt">// First loop: Find the most recent swing high(first high)
     for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
     {
       class=class="str">"cmt">// Check if the current bar is a swing high
       if(IsSwingHigh(high_price, i, LookbackBars))
       {
         class=class="str">"cmt">// Store the price and time of this latest swing high
         first_high = high_price[i];
         first_high_time = time_price[i];
         break; class=class="str">"cmt">// Exit loop once the first swing high is found
       }
     }
     class=class="str">"cmt">// Second loop: Find an earlier swing high that is higher than the first high and occurred before it

◍ 下降趋势线的第二个摆动高点定位

在已捕获第一个摆动高点后,代码从 LookbackBars 位置向前扫到 bars_check - LookbackBars,专门寻找时间更早、价格却更高的摆动高点。一旦通过 IsSwingHigh 判定且 high_price[i] > first_high 且时间更早,就记下 second_high 与 second_high_time 并 break,避免无意义遍历。 找到两个高点后,ObjectCreate 用 OBJ_TREND 在 chart_id 上连出趋势线,但先用 OBJPROP_TIMEFRAMES 设为 OBJ_NO_PERIODS 隐藏,防止在部分周期上画出半截线。 结构校验要求 first_high < second_high 且 second_high > 0,才确认是下降连线(后期高点更低)。通过后把 OBJPROP_RAY_RIGHT 开为真向右延伸,OBJPROP_TIMEFRAMES 改 OBJ_ALL_PERIODS 全周期可见,颜色 clrDarkGreen、宽度 3 像素,便于肉眼跟踪。 IsSwingLow 与 IsSwingHigh 是对称判定:以 index 为中心向左右各看 lookback 根 K 线,只要 low[index] 大于任一侧就非低点。实盘里 lookback 取 2~5 较稳,过大容易漏掉紧凑波段的高风险外汇与贵金属信号。 下面这段是原文里 High 判定的入口,Low 判定同理可对照抄写验证。

MQL5 / C++
for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
  {
   class=class="str">"cmt">// Check for earlier swing high that is higher and happened before the first one
   if(IsSwingHigh(high_price, i, LookbackBars) && high_price[i] > first_high && time_price[i] < first_high_time)
     {
      class=class="str">"cmt">// Store the price and time of this older swing high
      second_high = high_price[i];
      second_high_time = time_price[i];
      break;   class=class="str">"cmt">// Exit loop once the second swing high is found
     }
  }
class=class="str">"cmt">// Create a trend line object from the second swing high to the first swing high
ObjectCreate(chart_id, down_trend, OBJ_TREND, class="num">0, second_high_time, second_high, first_high_time, first_high);
class=class="str">"cmt">// Initially hide the trend line across all timeframes to avoid partial drawing
ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
class=class="str">"cmt">// Validate the swing structure:
class=class="str">"cmt">// The older swing high should be higher than the later swing high to confirm a descending trend line
if(first_high < second_high && second_high > class="num">0)
  {
   class=class="str">"cmt">// Extend the trend line indefinitely to the right for better visual guidance
   ObjectSetInteger(chart_id, down_trend, OBJPROP_RAY_RIGHT, true);
   class=class="str">"cmt">// Make the trend line visible on all chart timeframes
   ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
   class=class="str">"cmt">// Set the trend line class="type">color to dark green for clear distinction
   ObjectSetInteger(chart_id, down_trend, OBJPROP_COLOR, clrDarkGreen);
   class=class="str">"cmt">// Set the thickness of the trend line to class="num">3 pixels for better visibility
   ObjectSetInteger(chart_id, down_trend, OBJPROP_WIDTH, class="num">3);
  }
 }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| FUNCTION FOR LOWS                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool IsSwingLow(const class="type">class="kw">double &low[], class="type">int index, class="type">int lookback)
  {
   for(class="type">int i = class="num">1; i <= lookback; i++)
     {
      if(low[index] > low[index - i] || low[index] > low[index + i])
        class="kw">return false;
     }
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| FUNCTION FOR HIGHS                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool IsSwingHigh(const class="type">class="kw">double &high[], class="type">int index, class="type">int lookback)
  {

「回踩趋势线的多条件过滤」

这段逻辑在干一件事:只有当价格从上升趋势线外侧回踩、且近期没有重复触发时,才允许开多。外汇与贵金属杠杆高,这类信号失败概率不低,实盘前必须在 MT5 策略测试器跑一遍。 先看局部高点判定:以当前 K 线为锚,向左右各扩展 lookback 根,只要存在更高高点就返回 false,否则视为局部顶。

MQL5 / C++
for(class="type">int i = class="num">1; i <= lookback; i++)
  {
  if(high[index] < high[index - i] || high[index] < high[index + i])
    class="kw">return false;
  }
class="kw">return true;
逐行拆解:第1行从偏移1开始循环到lookback;第3行比较左右两侧高点,任一更高则否定;第5行全部更低才确认局部顶。 随后统计回踩有效性:在最近4根内,若某根最低价跌破 up_trend 趋势线但开盘价仍在线上,就向内回溯找第一根收阳且收在趋势线上的 K 线,用 Bars() 算出距当前有多少根。
MQL5 / C++
class="type">int no_bars = class="num">0;
for(class="type">int i = class="num">0; i <= class="num">3; i++)
  {
  if(low_price[i] < ObjectGetValueByTime(chart_id, up_trend, time_price[i], class="num">0) &&
      open_price[i] > ObjectGetValueByTime(chart_id, up_trend, time_price[i], class="num">0))
    {
    for(class="type">int j = i; j >= class="num">0; j--)
      {
      if(close_price[j] > open_price[j] &&
          close_price[j] > ObjectGetValueByTime(chart_id, up_trend, time_price[j], class="num">0))
        {
        no_bars = Bars(_Symbol, time_frame, time_price[j], TimeCurrent());
        break;
        }
      }
    break;
    }
  }
关键点:no_bars < 3 意味着回踩后3根内就出现阳线确认,属于紧凑型反应;若超过则可能倾向弱化。 开仓前还检查前两根是否已有触碰:prev_touch 在 low[1] 或 low[2] 曾刺穿 t1/t2 线且收阳时置真,本次要求它为 false,避免连续触发。
MQL5 / C++
class="type">bool prev_touch = false;
if((low_price[class="num">1] < t1_line_value && close_price[class="num">1] > open_price[class="num">1]) ||
   (low_price[class="num">2] < t2_line_value && close_price[class="num">2] > open_price[class="num">2])) {
    prev_touch = true;
}
最终买入判定把四档趋势线回踩、当根收阳、no_bars<3、无前触、时间窗及防重复开仓全串起来:
MQL5 / C++
if(
  ((low_price[class="num">0] < t_line_value && open_price[class="num">0] > t_line_value) ||
    (low_price[class="num">1] < t1_line_value && open_price[class="num">1] > t1_line_value) ||
    (low_price[class="num">2] < t2_line_value && open_price[class="num">2] > t2_line_value) ||
    (low_price[class="num">3] < t3_line_value && open_price[class="num">3] > t3_line_value))
  &&
  (close_price[class="num">0] > open_price[class="num">0]) && close_price[class="num">0] > t_line_value
  &&
  (no_bars < class="num">3)
  &&
  prev_touch == false
  &&
  (time_price[class="num">3] > lookbackf_time)
  &&
  (allow_reversal == true && currentBarTime != lastTradeBarTime)
  )
  {
  trade.Buy(lot_size, _Symbol, ask_price, ask_price - sl_points, ask_price + tp_points);
  lastTradeBarTime = currentBarTime;
  }
画上升线分两遍扫:第一遍从 LookbackBars 扫到 bars_check-LookbackBars,用 IsSwingLow 抓最近摆动低点存为 first_low;第二遍找更早且更低的摆动低点。
MQL5 / C++
if(allow_uptrend)
  {
  for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
    {
    if(IsSwingLow(low_price, i, LookbackBars))
      {
      first_low = low_price[i];
      first_low_time = time_price[i];
      lookbackf_time = time_price[i - class="num">3];
      break;
      }
    }
  for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
    {
    if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time)
      {
把 LookbackBars 调到 5 或 8,连线斜率与假突破次数会明显不同,建议直接改参数对比两图。

MQL5 / C++
for(class="type">int i = class="num">1; i <= lookback; i++)
  {
  if(high[index] < high[index - i] || high[index] < high[index + i])
    class="kw">return false;
  }
class="kw">return true;
class="type">int no_bars = class="num">0;
for(class="type">int i = class="num">0; i <= class="num">3; i++)
  {
  if(low_price[i] < ObjectGetValueByTime(chart_id, up_trend, time_price[i], class="num">0) &&
      open_price[i] > ObjectGetValueByTime(chart_id, up_trend, time_price[i], class="num">0))
    {
    for(class="type">int j = i; j >= class="num">0; j--)
      {
      if(close_price[j] > open_price[j] &&
          close_price[j] > ObjectGetValueByTime(chart_id, up_trend, time_price[j], class="num">0))
        {
        no_bars = Bars(_Symbol, time_frame, time_price[j], TimeCurrent());
        break;
        }
      }
    break;
    }
  }
class="type">bool prev_touch = false;
if((low_price[class="num">1] < t1_line_value && close_price[class="num">1] > open_price[class="num">1]) ||
   (low_price[class="num">2] < t2_line_value && close_price[class="num">2] > open_price[class="num">2])) {
    prev_touch = true;
}
if(
  ((low_price[class="num">0] < t_line_value && open_price[class="num">0] > t_line_value) ||
    (low_price[class="num">1] < t1_line_value && open_price[class="num">1] > t1_line_value) ||
    (low_price[class="num">2] < t2_line_value && open_price[class="num">2] > t2_line_value) ||
    (low_price[class="num">3] < t3_line_value && open_price[class="num">3] > t3_line_value))
  &&
  (close_price[class="num">0] > open_price[class="num">0]) && close_price[class="num">0] > t_line_value
  &&
  (no_bars < class="num">3)
  &&
  prev_touch == false
  &&
  (time_price[class="num">3] > lookbackf_time)
  &&
  (allow_reversal == true && currentBarTime != lastTradeBarTime)
  )
  {
  trade.Buy(lot_size, _Symbol, ask_price, ask_price - sl_points, ask_price + tp_points);
  lastTradeBarTime = currentBarTime;
  }
if(allow_uptrend)
  {
  for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
    {
    if(IsSwingLow(low_price, i, LookbackBars))
      {
      first_low = low_price[i];
      first_low_time = time_price[i];
      lookbackf_time = time_price[i - class="num">3];
      break;
      }
    }
  for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
    {
    if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time)
      {

上升趋势线的下影回踩判定

在找到第二个摆动低点后,代码先把它和第一个低点连成一条上升趋势线,并用 OBJ_TREND 创建、OBJ_NO_PERIODS 暂时隐藏,避免结构未确认前干扰视图。 只有当 first_low > second_low 且 second_low > 0 时,才把线向右延伸(RAY_RIGHT=true)、切到 OBJ_ALL_PERIODS 全周期显示,并设成蓝色 3 像素宽——这是多头结构成立的硬门槛。 随后取最近 4 根 K 线对应趋势线价位:t_line_value 到 t3_line_value 分别绑定 time_price[0]~[3]。循环里判定「下影刺穿但开盘在线上的回踩」:low_price[i] 低于线值且 open_price[i] 高于线值,倾向视为潜在反转区。 紧接着内层循环从 i 往当前扫,若存在 close_price[j] > open_price[j] 且收在线上,才算一根看涨确认蜡烛。外汇与贵金属波动剧烈,这种刺穿+收上行为只是概率信号,实盘前请在 MT5 用历史品种跑一遍验证触发频率。

MQL5 / C++
second_low = low_price[i];
second_low_time = time_price[i];
break;
}
}
ObjectCreate(chart_id, up_trend, OBJ_TREND, class="num">0, second_low_time, second_low, first_low_time, first_low);
ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
if(first_low > second_low && second_low > class="num">0)
  {
  ObjectSetInteger(chart_id, up_trend, OBJPROP_RAY_RIGHT, true);
  ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
  ObjectSetInteger(chart_id, up_trend, OBJPROP_COLOR, clrBlue);
  ObjectSetInteger(chart_id, up_trend, OBJPROP_WIDTH, class="num">3);
  t_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">0], class="num">0);
  t1_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">1], class="num">0);
  t2_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">2], class="num">0);
  t3_line_value = ObjectGetValueByTime(chart_id, up_trend, time_price[class="num">3], class="num">0);
  class="type">int no_bars = class="num">0;
  for(class="type">int i = class="num">0; i <= class="num">3; i++)
    {
    if(low_price[i] < ObjectGetValueByTime(chart_id, up_trend, time_price[i], class="num">0) &&
        open_price[i] > ObjectGetValueByTime(chart_id, up_trend, time_price[i], class="num">0))
      {
      for(class="type">int j = i; j >= class="num">0; j--)
        {
        if(close_price[j] > open_price[j] &&
            close_price[j] > ObjectGetValueByTime(chart_id, up_trend, time_price[j], class="num">0))

◍ 反转确认的去重与买点触发

这段逻辑卡的是「趋势线刺透后三日内的看涨反转」买点。它先用 no_bars = Bars(_Symbol, time_frame, time_price[j], TimeCurrent()) 算出确认信号距离当前过了多少根 K 线,只在 no_bars < 3 时才算有效,避免迟到信号在 MT5 上误触发。 为了防止同一处刺透反复报警,代码用 prev_touch 标记近期是否已出现过下影线 rejection:若第 1 根或第 2 根 K 线曾跌破 t1/t2 线且收阳,就把 prev_touch 置 true,后续同区信号直接屏蔽。 最终 Buy 条件是一条串起来的与逻辑:近 4 根中至少一根下影刺穿对应趋势线且开盘在线上方、当前 K 线收阳且收盘价站上 t_line_value、确认发生在 3 根内、prev_touch 为 false、信号时间晚于 lookbackf_time、且 allow_reversal 开启并排除同根 K 重复发单。 满足条件后 trade.Buy(lot_size, _Symbol, ask_price, ask_price - sl_points, ask_price + tp_points) 按预设手数、止损、止盈下单,并刷新 lastTradeBarTime。外汇与贵金属杠杆高,这类反转触发可能失效,实盘前请在策略测试器用不同 time_frame 跑一遍 no_bars 阈值。

MQL5 / C++
         {
                  class=class="str">"cmt">// Count how many bars ago this confirmation occurred
                  no_bars = Bars(_Symbol, time_frame, time_price[j], TimeCurrent());
                  break;
               }
            break; class=class="str">"cmt">// Exit after first valid reversal zone is found
            }
         class=class="str">"cmt">// Check whether a similar wick touch(reversal) happened recently to avoid repeated signals
         class="type">bool prev_touch = false;
         if((low_price[class="num">1] < t1_line_value && close_price[class="num">1] > open_price[class="num">1]) ||  class=class="str">"cmt">// Bar class="num">1 had reversal wick and bullish body
             (low_price[class="num">2] < t2_line_value && close_price[class="num">2] > open_price[class="num">2]))    class=class="str">"cmt">// Bar class="num">2 had reversal wick and bullish body
            {
             prev_touch = true;  class=class="str">"cmt">// Flag that a recent touch already occurred
            }
         class=class="str">"cmt">// Final condition for executing a BUY trade on a reversal setup
         if(
            class=class="str">"cmt">// One of the recent class="num">4 bars touched and rejected the trend line(wick below, open above), AND
            ((low_price[class="num">0] < t_line_value && open_price[class="num">0] > t_line_value) ||
             (low_price[class="num">1] < t1_line_value && open_price[class="num">1] > t1_line_value) ||
             (low_price[class="num">2] < t2_line_value && open_price[class="num">2] > t2_line_value) ||
             (low_price[class="num">3] < t3_line_value && open_price[class="num">3] > t3_line_value))
            &&
            class=class="str">"cmt">// Current candle must be bullish and close above the trend line
            (close_price[class="num">0] > open_price[class="num">0]) && close_price[class="num">0] > t_line_value
            &&
            class=class="str">"cmt">// The bullish confirmation must occur within class="num">3 bars
            (no_bars < class="num">3)
            &&
            class=class="str">"cmt">// No recent wick reversal signal already processed
            prev_touch == false
            &&
            class=class="str">"cmt">// The signal must be more recent than the lookback time threshold
            (time_price[class="num">3] > lookbackf_time)
            &&
            class=class="str">"cmt">// Reversal signals are allowed and this signal is not duplicated from the same bar
            (allow_reversal == true && currentBarTime != lastTradeBarTime)
            )
            {
             class=class="str">"cmt">// Execute BUY trade with defined lot size, SL and TP
             trade.Buy(lot_size, _Symbol, ask_price, ask_price - sl_points, ask_price + tp_points);
             lastTradeBarTime = currentBarTime; class=class="str">"cmt">// Update last trade bar time to avoid duplicate signals
            }
         class=class="str">"cmt">//BREAKOUT AND RETEST

「上升趋势线假突破的空头确认逻辑」

在 MT5 里做趋势线破位回测,最怕把「刺穿影线」误判成有效跌破。下面这段逻辑专门捕捉上升趋势线被上方影线触碰、但实体仍收在趋势线之下的假突破后空头信号。 先用 prev_touch2 标记最近是否已有一次看跌影线拒绝,避免同一形态反复报警。接着扫最近 2 根 K 线:若第 1 根高点越过 t1_line_value 且收盘低于开盘,或第 2 根高点越过 t2_line_value、收盘低于开盘且开盘本身就在线下方,就置 prev_touch2 = true。 主循环只翻最近 4 根蜡烛。某根 high 大于 ObjectGetValueByTime 取到的趋势线值、但 open 小于该值,说明影线刺穿后回落;再从这根向前找收阴且收盘跌破趋势线的确认棒,用 Bars() 算它距当前多少根。若 no_bars2 落在 3 根内、当前棒收阴跌破线、且 prev_touch2 为假,才认定为可交易回测setup。外汇与贵金属杠杆高,此类假突破反转失败概率不低,实盘前务必在策略测试器跑足样本。

MQL5 / C++
class="type">bool prev_touch2 = false;
if((high_price[class="num">1] > t1_line_value && close_price[class="num">1] < open_price[class="num">1]) ||
   (high_price[class="num">2] > t2_line_value && close_price[class="num">2] < open_price[class="num">2] && open_price[class="num">2] < t2_line_value))
   {
   prev_touch2 = true; class=class="str">"cmt">// Set flag to avoid duplicate signals
   }
class="type">int no_bars2 = class="num">0;
for(class="type">int i = class="num">0; i <= class="num">3; i++)
   {
   if(high_price[i] > ObjectGetValueByTime(chart_id, up_trend, time_price[i], class="num">0) &&
      open_price[i] < ObjectGetValueByTime(chart_id, up_trend, time_price[i], class="num">0))
      {
      for(class="type">int j = i; j >= class="num">0; j--)
         {
         if(close_price[j] < open_price[j] &&
            close_price[j] < ObjectGetValueByTime(chart_id, up_trend, time_price[j], class="num">0))
            {
            no_bars2 = Bars(_Symbol, time_frame, time_price[j], TimeCurrent());
            break;
            }
         }
      break;
      }
   }

下降趋势线的双顶摆动识别与绘制

在允许绘制下降趋势线时,脚本先用一轮循环从 LookbackBars 扫到 bars_check-LookbackBars,找最近的摆动高点(swing high)作为 first_high,并记录其时间。找到后立即 break,避免无谓遍历。 第二轮的判定更苛刻:必须是更早出现、且价格高于 first_high 的摆动高点,存为 second_high。只有「旧高 > 后来新高」这种结构,才能确认是一条真正向下的趋势线,而不是横盘里的噪声。 画线时用 ObjectCreate 以 second_high 连到 first_high,先设 OBJPROP_TIMEFRAMES 为 OBJ_NO_PERIODS 隐藏,等结构校验通过(first_high < second_high 且 second_high>0)才开 OBJPROP_RAY_RIGHT 向右延伸,并切到 OBJ_ALL_PERIODS 全周期可见。 前面卖单触发段也依赖这类线值:当 2 号或 3 号 K 线最高价刺穿对应 t 线而开盘在线下、当前 K 收盘回落且 no_bars2<3、prev_touch2 为假、允许突破时,才用 trade.Sell 按 ask+sl_points / ask-tp_points 下单,并写 lastTradeBarTime 防同根 K 重复信号。外汇与贵金属杠杆高,此类信号失效概率不低,实盘前务必在 MT5 策略测试器跑历史数据验证。

MQL5 / C++
if(allow_downtrend)
  {
  for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
    {
    if(IsSwingHigh(high_price, i, LookbackBars))
      {
      first_high = high_price[i];
      first_high_time = time_price[i];
      break;
      }
    }
  for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++)
    {
    if(IsSwingHigh(high_price, i, LookbackBars) && high_price[i] > first_high && time_price[i] < first_high_time)
      {
      second_high = high_price[i];
      second_high_time = time_price[i];
      break;
      }
    }
  ObjectCreate(chart_id, down_trend, OBJ_TREND, class="num">0, second_high_time, second_high, first_high_time, first_high);
  ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
  if(first_high < second_high && second_high > class="num">0)
    {
    ObjectSetInteger(chart_id, down_trend, OBJPROP_RAY_RIGHT, true);
    ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
    }
  }

◍ 下破趋势线的反转触发判定

这段逻辑针对一条已绘制的下降趋势线(down_trend),先把它设为深绿色、线宽 3 像素,保证在 MT5 图表上肉眼可辨。随后用 ObjectGetValueByTime 抓取 time_price[0]~[3] 四个时刻趋势线的价格,分别存入 td_line_value 到 td3_line_value,作为后续刺穿判定的基准线。 核心是一个双重循环:外层遍历最近 4 根 K 线,若某根 high 站上趋势线而 open 在线下(向上刺穿),内层再往前找一根收盘为阴线且收在线下的 K 线,用 Bars() 算出距当前相隔多少根,赋给 no_bars。若 no_bars < 3,说明刺穿后很快被空头反压,倾向视为有效反转信号。 另外用 prev_touch 标记前 1~2 根是否已有刺穿未收破的情况,避免重复触发。最终判定要求:最近 1~3 根任一根向上刺穿、当前根收盘在线下且为阴线、前一根 open 也在线下、no_bars < 3、prev_touch 为假、且允许反转开关打开并避开同根 K 线重复交易——全部满足才进入信号块。外汇与贵金属波动剧烈,该条件仅提高概率,实盘须用小周期回测验证。

MQL5 / C++
ObjectSetInteger(chart_id, down_trend, OBJPROP_COLOR, clrDarkGreen);
class=class="str">"cmt">// Set the thickness of the trend line to class="num">3 pixels for better visibility
ObjectSetInteger(chart_id, down_trend, OBJPROP_WIDTH, class="num">3);
class=class="str">"cmt">//REVERSAL
td_line_value = ObjectGetValueByTime(chart_id,down_trend,time_price[class="num">0],class="num">0);
td1_line_value = ObjectGetValueByTime(chart_id,down_trend,time_price[class="num">1],class="num">0);
td2_line_value = ObjectGetValueByTime(chart_id,down_trend,time_price[class="num">2],class="num">0);
td3_line_value = ObjectGetValueByTime(chart_id,down_trend,time_price[class="num">3],class="num">0);
class="type">int no_bars = class="num">0;
for(class="type">int i = class="num">0; i <= class="num">3; i++)
  {
   if(high_price[i] > ObjectGetValueByTime(chart_id,down_trend,time_price[i],class="num">0) && open_price[i] < ObjectGetValueByTime(chart_id,down_trend,time_price[i],class="num">0)
     )
     {
      for(class="type">int j = i; j >= class="num">0; j--)
       {
        if(close_price[j] < open_price[j] && close_price[j] < ObjectGetValueByTime(chart_id,down_trend,time_price[j],class="num">0))
          {
           no_bars = Bars(_Symbol,time_frame,time_price[j],TimeCurrent());
           break;
          }
       }
      break;
     }
  }
class="type">bool prev_touch = false;
if((high_price[class="num">1] > td1_line_value && close_price[class="num">1] < open_price[class="num">1])
   ||
   (high_price[class="num">2] > td2_line_value && close_price[class="num">2] < open_price[class="num">2])
   )
   {
    prev_touch = true;
   }
if(((high_price[class="num">1] >= td1_line_value && open_price[class="num">1] < td1_line_value) || (high_price[class="num">2] >= td2_line_value && open_price[class="num">2] < td2_line_value)
   || (high_price[class="num">3] >= td3_line_value && open_price[class="num">3] < td3_line_value) || (high_price[class="num">0] >= td_line_value))
   && (close_price[class="num">0] < td_line_value && close_price[class="num">0] < open_price[class="num">0] && open_price[class="num">1] < td1_line_value)
   && (no_bars < class="num">3)
   && prev_touch == false
   && (allow_reversal == true  && currentBarTime != lastTradeBarTime)
   )
   {

「下降趋势线的下破回测买点判定」

在下跌趋势线策略里,突破回测的买点不是看价格碰一下线就进场,而是要先确认近期存在“下影线刺穿但实体收在线上方”的拒绝动作。代码用 prev_touch2 标记最近两根 K 线(索引 1、2)是否已出现过此类看涨拒绝,避免同一形态反复发信号。 具体扫描时,对最近 4 根 K 线(i=0 到 3)做外循环:当某根 low 低于趋势线值、但 open 高于趋势线值时,再向内回溯 j 寻找一根收盘大于开盘且收盘站上趋势线的看涨确认 candle,并用 Bars() 算出它距当前有多少根。若 no_bars2 落在最近 3 根以内、当前 candle 为阳线且收在线上、且 prev_touch2 仍为 false,才构成有效的 BUY 回测确认。 外汇与贵金属杠杆高,这类形态失效时反向破位概率不低,实盘前应在 MT5 用历史数据把 td1_line_value / td2_line_value 与循环窗口跑一遍,确认在你的周期上假信号频率可接受。

MQL5 / C++
      trade.Sell(lot_size,_Symbol,ask_price,ask_price + sl_points, ask_price - tp_points);
      lastTradeBarTime = currentBarTime;
      }
      class=class="str">"cmt">//BREAKOUT AMD RETEST
      class=class="str">"cmt">// Flag to track whether a recent bullish wick rejection(touch) already occurred
      class="type">bool prev_touch2 = false;
      class=class="str">"cmt">// Check the last class="num">2 candles for bullish rejection from below the descending trend line
      class=class="str">"cmt">// A bullish rejection occurs when the low goes below the trend line but closes above the open(bullish candle)
      if((low_price[class="num">1] < td1_line_value && close_price[class="num">1] > open_price[class="num">1]) ||
         (low_price[class="num">2] < td2_line_value && close_price[class="num">2] > open_price[class="num">2] && open_price[class="num">2] > td2_line_value))
         {
         prev_touch2 = true; class=class="str">"cmt">// Set flag to prevent duplicate signals from the same type of setup
         }
      class=class="str">"cmt">// Variable to hold how many bars ago a bullish confirmation candle occurred after wick rejection
      class="type">int no_bars2 = class="num">0;
      class=class="str">"cmt">// Loop through the last class="num">4 candles to detect a wick rejection of the descending trend line
      for(class="type">int i = class="num">0; i <= class="num">3; i++)
         {
         class=class="str">"cmt">// Condition: Candle wick(low) goes below the trend line, but the open is above it
         if(low_price[i] < ObjectGetValueByTime(chart_id, down_trend, time_price[i], class="num">0) &&
            open_price[i] > ObjectGetValueByTime(chart_id, down_trend, time_price[i], class="num">0))
           {
           class=class="str">"cmt">// Look backward for a bullish confirmation candle that closes above the trend line
           for(class="type">int j = i; j >= class="num">0; j--)
             {
             if(close_price[j] > open_price[j] &&
                close_price[j] > ObjectGetValueByTime(chart_id, down_trend, time_price[j], class="num">0))
                {
                class=class="str">"cmt">// Count how many bars ago that bullish confirmation happened
                no_bars2 = Bars(_Symbol, time_frame, time_price[j], TimeCurrent());
                break; class=class="str">"cmt">// Exit inner loop once confirmation is found
                }
             }
           break; class=class="str">"cmt">// Exit outer loop after the first valid retest is processed
           }
         }
      class=class="str">"cmt">// Final conditions to confirm a breakout or retest for a BUY setup on descending trend line:
      class=class="str">"cmt">// class="num">1. One of the last class="num">4 candles had a wick below the trend line but opened above it
      class=class="str">"cmt">// class="num">2. Current candle is bullish and closed above the trend line
      class=class="str">"cmt">// class="num">3. A valid bullish confirmation occurred within the last class="num">3 bars
      class=class="str">"cmt">// class="num">4. No recent similar touch detected(prev_touch2 == false)

下破后回拉的多头触发判定

这段代码是趋势线向下突破失败后的多头入场逻辑,核心看最近 4 根 K 线中是否有任一根开盘在 TD 线之上、最低刺穿该线,且当前 bar 收阳并站回 td_line_value 上方。 条件里用 no_bars2 < 3 限制脱钩计数,prev_touch2 == false 确保前一根未触碰,time_price[3] > lookbackfd_time 卡掉过旧的信号——这几道门槛同时过,才允许发单。 外汇与贵金属杠杆高,刺穿回拉属于概率型反转信号,实盘须用迷你手数验证;MT5 里把 sl_points / tp_points 调成你惯用的 ATR 倍数,再开策略测试器跑 EURUSD 的 M15 看成交密度。 别把刺穿当反转铁证 最低价穿过线但收盘没站回去的 bar,在这套逻辑里直接被拒,说明编写者把“收回”当成确认,而不是“碰线”就动手。

MQL5 / C++
  class=class="str">"cmt">// class="num">5. Candle timestamps are valid(not too far back)
  class=class="str">"cmt">// class="num">6. Breakout trading is allowed, and this bar is not the same as the last trade bar
  if(
      ((low_price[class="num">0] < td_line_value && open_price[class="num">0] > td_line_value) ||
       (low_price[class="num">1] < td1_line_value && open_price[class="num">1] > td1_line_value) ||
       (low_price[class="num">2] < td2_line_value && open_price[class="num">2] > td2_line_value) ||
       (low_price[class="num">3] < td3_line_value && open_price[class="num">3] > td3_line_value)) &&
      (close_price[class="num">0] > open_price[class="num">0]) && close_price[class="num">0] > td_line_value &&
      (no_bars2 < class="num">3) &&
      prev_touch2 == false &&
      (time_price[class="num">3] > lookbackfd_time) &&
      (allow_break_out == true && currentBarTime != lastTradeBarTime)
      )
      {
      class=class="str">"cmt">// All conditions met - place a BUY trade with defined SL and TP
      trade.Buy(lot_size, _Symbol, ask_price, ask_price - sl_points, ask_price + tp_points);
      class=class="str">"cmt">// Update the last trade time to avoid repeated trades from the same bar
      lastTradeBarTime = currentBarTime;
      }

◍ 把工具请下神坛

趋势线逻辑跑通之后,EA 只是帮你把通道、升降趋势线的突破与反转条件从手动比对变成实时响应,附件里的 Project_11_Trend_Line_EA.mq5 约 47 KB,可直接丢进 MT5 回测验证。 这套检索趋势线数值并比价执行的框架,也能平移到三角形、楔形或双顶双底;但外汇与贵金属杠杆高、滑点跳空频繁,自动交互触发不代表胜率有保证,停产或过度拟合都可能让你在实盘吃亏。 真要落地,先拿英镑兑美元这类低点差品种跑历史数据,看看突破过滤参数在你的 broker 环境里是不是还灵,再谈挂真金白银。

常见问题

取最近四根柱,若价格跌破由两摆动低点连成的上升线且最后一根收在线下,可视为突破;配合回踩长影线反转形态提高概率,建议先在图表手动验证再写逻辑。
是的,标准做法是用两次摆动低点连线。写代码时从左到右扫描谷底,记录最近两个更低低点坐标,连成线即可,注意过滤小幅噪声。
可以,小布盯盘的AIGC看盘能自动识别摆动低点连线标出趋势线,并在出现回踩长影线时推送反转提醒,省去手动写EA的麻烦。
把摆动点数、影线最小长度、线条颜色分开成输入组,用结构体存高低点坐标,避免全局变量散落,方便后续调参和回测。
要求价格刺穿趋势线后快速收回,且影线长度大于实体的两倍并收在线另一侧,这种形态才倾向反转,单根小影线不算。