How to Generate Only ONE Signal Per Confirmed Trend (Swing + EMA Based)?

 

Hi all,

I’m working on a custom MQL4 swing-based indicator that should generate only one clean BUY signal when a bullish trend is confirmed, and only one SELL signal when a bearish trend is confirmed — then stay silent until the trend actually reverses.

//+------------------------------------------------------------------+
//|                                            SwingSignal.mq4       |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Swing Signal Indicator"
#property link      ""
#property version   "2.00"
#property strict
#property indicator_chart_window

input int SwingPeriod = 5;        
input int EMAPeriod = 8;          
input color BuyColor = clrBlue;   
input color SellColor = clrRed;   
input int ArrowSize = 2;          

struct SwingPoint {
   double price;
   int bar;
};

SwingPoint swingHighs[];
SwingPoint swingLows[];

int OnInit()
{
   ObjectsDeleteAll(0, "SWING_");
   return(INIT_SUCCEEDED);
}

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total < SwingPeriod * 2 + EMAPeriod + 20)
      return(0);
   
   static bool firstRun = true;
   if(firstRun)
   {
      ObjectsDeleteAll(0, "SWING_");
      ScanAllChart();
      firstRun = false;
   }
   
   return(rates_total);
}

void ScanAllChart()
{
   int totalBars = Bars - SwingPeriod - 20;
   int lastTrendDetected = 0;
   bool signalGivenInThisTrend = false;
   
   for(int i = totalBars; i >= SwingPeriod + 2; i--)
   {
      FindSwingsUpTo(i);
      int highCount = ArraySize(swingHighs);
      int lowCount = ArraySize(swingLows);
      if(highCount < 2 || lowCount < 2) continue;
      
      bool isUptrend = (swingHighs[highCount-1].price > swingHighs[highCount-2].price &&
                        swingLows[lowCount-1].price > swingLows[lowCount-2].price);
      bool isDowntrend = (swingHighs[highCount-1].price < swingHighs[highCount-2].price &&
                          swingLows[lowCount-1].price < swingLows[lowCount-2].price);
      
      int currentTrend = 0;
      if(isUptrend && !isDowntrend) currentTrend = 1;
      else if(isDowntrend && !isUptrend) currentTrend = -1;
      
      if(currentTrend != 0 && currentTrend != lastTrendDetected)
      {
         lastTrendDetected = currentTrend;
         signalGivenInThisTrend = false;
      }
      
      if(!signalGivenInThisTrend && currentTrend != 0)
      {
         double ema8 = iMA(NULL, 0, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE, i);
         if(currentTrend == 1)
         {
            double lastSwingLow = swingLows[lowCount-1].price;
            if(Close[i] > ema8 && Low[i] > lastSwingLow)
            {
               string name = "SWING_BUY_" + IntegerToString(i) + "_" + IntegerToString(Time[i]);
               DrawArrow(name, Time[i], Low[i] - 10*Point, BuyColor, 233, "BUY");
               signalGivenInThisTrend = true;
            }
         }
         else if(currentTrend == -1)
         {
            double lastSwingHigh = swingHighs[highCount-1].price;
            if(Close[i] < ema8 && High[i] < lastSwingHigh)
            {
               string name = "SWING_SELL_" + IntegerToString(i) + "_" + IntegerToString(Time[i]);
               DrawArrow(name, Time[i], High[i] + 10*Point, SellColor, 234, "SELL");
               signalGivenInThisTrend = true;
            }
         }
      }
   }
}

void FindSwingsUpTo(int endBar)
{
   ArrayFree(swingHighs);
   ArrayFree(swingLows);
   int swingHighCount = 0, swingLowCount = 0;
   int startBar = MathMin(Bars - SwingPeriod - 10, endBar + 100);
   
   for(int i = startBar; i >= endBar; i--)
   {
      if(i < SwingPeriod || i > Bars - SwingPeriod) continue;
      
      bool isSwingHigh = true;
      double centerHigh = High[i];
      for(int j = 1; j <= SwingPeriod; j++)
      {
         if(i-j < 0 || i+j >= Bars || High[i-j] >= centerHigh || High[i+j] >= centerHigh)
         {
            isSwingHigh = false; break;
         }
      }
      if(isSwingHigh)
      {
         ArrayResize(swingHighs, swingHighCount + 1);
         swingHighs[swingHighCount].price = centerHigh;
         swingHighs[swingHighCount].bar = i;
         swingHighCount++;
      }
      
      bool isSwingLow = true;
      double centerLow = Low[i];
      for(int j = 1; j <= SwingPeriod; j++)
      {
         if(i-j < 0 || i+j >= Bars || Low[i-j] <= centerLow || Low[i+j] <= centerLow)
         {
            isSwingLow = false; break;
         }
      }
      if(isSwingLow)
      {
         ArrayResize(swingLows, swingLowCount + 1);
         swingLows[swingLowCount].price = centerLow;
         swingLows[swingLowCount].bar = i;
         swingLowCount++;
      }
   }
}

void DrawArrow(string name, datetime time, double price, color clr, int arrowCode, string text)
{
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_ARROW, 0, time, price);
      ObjectSetInteger(0, name, OBJPROP_ARROWCODE, arrowCode);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, ArrowSize);
      ObjectSetString(0, name, OBJPROP_TEXT, text);
   }
}

void OnDeinit(const int reason)
{
   ObjectsDeleteAll(0, "SWING_");
}
//+------------------------------------------------------------------+


 My Signal Rules (exactly as coded):

BUY Signal Conditions

  1. Bullish trend confirmed:
    • Two consecutive Higher Highs
    • Two consecutive Higher Lows
  2. On the first fully closed candle where:
    • Close > EMA(8)
    • Low[candle] > most recent swing low
      Candle color is ignored (only Close vs EMA matters)

SELL Signal Conditions

  1. Bearish trend confirmed:
    • Two consecutive Lower Highs
    • Two consecutive Lower Lows
  2. On the first fully closed candle where:
    • Close < EMA(8)
    • High[candle] < most recent swing high

The Problem (in practice):

Even though I use:

  • lastTrendDetected to track trend direction ( 0 = none, 1 = bullish, -1 = bearish )
  • signalGivenInThisTrend flag to allow only one signal per trend

...the indicator still plots multiple arrows in the same trend phase.

Root Cause (I suspect):
My logic scans the chart backward ( for i = totalBars; i >= ... ) and rebuilds all swing points up to each historical bar using FindSwingsUpTo(i) .
This causes the set of detected swings — and therefore the trend state — to change artificially at different historical bars, tricking the system into thinking the trend "restarted" and triggering extra signals.

For example:

  • At bar 100: trend = BULLISH → signal given 
  • At bar 95: only 1 swing low detected → trend = NONE
  • At bar 90: 2 swing lows again → trend = BULLISH → second signal 

But in reality, the trend never reversed — it’s just an artifact of inconsistent swing detection across time.

How can I restructure this logic to detect swings consistently over the entire chart, and ensure that ONLY ONE signal is generated per full trend cycle — without artificial re-triggers caused by historical swing recalculation?

I believe the solution involves:

  • Building the full list of swing points once (forward scan)
  • Then evaluating trend changes in chronological order
  • And locking signals until a true reversal (not temporary swing gaps)

Any guidance or code pattern would be greatly appreciated!

Thanks for your time! 🙏

 

this is how to fix your logic (while making the code close to how you have it)

void ScanAllChart()
  {
   int totalBars = Bars - SwingPeriod - 50;

   for(int i = totalBars; i >= SwingPeriod + 2; i--)
     {
      FindSwingsUpTo(i);
      int highCount = ArraySize(swingHighs);
      int lowCount = ArraySize(swingLows);
      if(ArraySize(swingHighs) < 2 || ArraySize(swingLows) < 2)
        {
         lastTrendDetected  = 0;
         continue;
        }
      double ema8 = iMA(NULL,0,EMAPeriod,0,MODE_EMA,PRICE_CLOSE,i);
      double lastSwingLow  = swingLows[lowCount-1].price;
      double lastSwingHigh = swingHighs[highCount-1].price;

      // --- Determine current bar's trend ---
      int currentTrend = 0;

      bool bullishCondition  = (Close[i] > ema8 && Low[i] > lastSwingLow);
      bool bearishCondition  = (Close[i] < ema8 && High[i] < lastSwingHigh);

      if(bullishCondition)
         currentTrend =  1;
      if(bearishCondition)
         currentTrend = -1;

      bool SignalGivenInThisTrend = false;

      if(currentTrend != 0 && currentTrend != lastTrendDetected)
        {
         SignalGivenInThisTrend = true;
        }

      if(SignalGivenInThisTrend)
        {
         string name = "SWING_" + (currentTrend==1?"BUY_":"SELL_") +
                       IntegerToString(i) + "_" + IntegerToString(Time[i]);
         if(currentTrend == 1)
            DrawArrow(name, Time[i], Low[i] - 15*Point, BuyColor, 233, "BUY");
         else
            if(currentTrend == -1)
               DrawArrow(name, Time[i], High[i] + 15*Point, SellColor, 234, "SELL");

        }

      // --- Update the last trend detected ---
      if(currentTrend != 0)
         lastTrendDetected = currentTrend;
     }
  }


You can also do it that way:

void ScanAllChart()
  {
   int totalBars = Bars - SwingPeriod - 50;

   for(int i = totalBars; i >= SwingPeriod + 2; i--)
     {
      FindSwingsUpTo(i);
      int highCount = ArraySize(swingHighs);
      int lowCount = ArraySize(swingLows);
      if(ArraySize(swingHighs) < 2 || ArraySize(swingLows) < 2)
        {
         lastTrendDetected  = 0;
         continue;
        }
      double ema8 = iMA(NULL,0,EMAPeriod,0,MODE_EMA,PRICE_CLOSE,i);
      double lastSwingLow  = swingLows[lowCount-1].price;
      double lastSwingHigh = swingHighs[highCount-1].price;

      // --- Determine current bar's trend ---
      int currentTrend = 0;

      bool bullishCondition  = (Close[i] > ema8 && Low[i] > lastSwingLow);
      bool bearishCondition  = (Close[i] < ema8 && High[i] < lastSwingHigh);

      if(bullishCondition)
         currentTrend =  1;
      if(bearishCondition)
         currentTrend = -1;

      bool SignalGivenInThisTrend = false;

      if(currentTrend != 0 && currentTrend != lastTrendDetected)
        {
         SignalGivenInThisTrend = true;
         lastTrendDetected = currentTrend;
        }

      if(SignalGivenInThisTrend)
        {
         string name = "SWING_" + (currentTrend==1?"BUY_":"SELL_") +
                       IntegerToString(i) + "_" + IntegerToString(Time[i]);
         if(currentTrend == 1)
            DrawArrow(name, Time[i], Low[i] - 15*Point, BuyColor, 233, "BUY");
         else
            if(currentTrend == -1)
               DrawArrow(name, Time[i], High[i] + 15*Point, SellColor, 234, "SELL");

        }
     }
  }


I would make this a global variable:

int lastTrendDetected = 0;